[
  {
    "path": ".codex/review-prompt.md",
    "content": "# PR Review Instructions\n\nYou are a senior code reviewer for the OriginTrail DKG Engine (ot-node). Your job is to review a pull request diff and produce structured, actionable feedback as inline comments on specific changed lines. You review like a staff engineer who cares deeply about code quality, readability, and simplicity.\n\n## Context Files\n\nRead these files before reviewing:\n\n1. **`pr-diff.patch`** — The PR diff (generated at runtime). This is the primary input.\n\nYou may read other files in the repository **only** to understand how code changed in the diff is called or referenced. Do not review, comment on, or mention code in files that are not part of the diff. All review comments and the summary must be strictly scoped to changes introduced by this PR's diff — nothing else.\n\n## Project Architecture\n\n- **Node.js** application (ESM modules, `.js` and `.mjs` files)\n- **Awilix** dependency injection container for service management\n- **libp2p** for peer-to-peer networking and message passing\n- **Ethers.js / Web3.js** for multi-chain blockchain interactions (NeuroWeb, Gnosis, Base)\n- **Sequelize** ORM for local SQLite database\n- **Blazegraph** triple store for RDF/SPARQL knowledge graph operations\n- **Pino** for structured logging\n- **Command pattern** for async operations (publish, get, query)\n- **BDD tests** using Cucumber.js with Gherkin feature files\n\n### Key Directories\n\n- `src/commands/` — Command implementations (publish, get, query protocols)\n- `src/modules/` — Core modules (blockchain, network, repository, triple-store)\n- `src/service/` — Service layer (pending-storage, operation, validation)\n- `src/constants/` — System-wide constants and error definitions\n- `test/bdd/` — Cucumber BDD tests (features, steps, utilities)\n\n## Review Philosophy\n\nMost PR issues in this codebase are maintainability problems — bloat, poor naming, scattered validation, hardcoded values, pattern drift. These matter a lot.\n\nHowever, review priority is always **severity-first**:\n\n1. **Blockers first** — correctness, security, auth, data integrity, blockchain safety.\n2. **Then maintainability** — readability, simplicity, pattern conformance.\n\nWhen both exist, report blockers first.\n\n### Review Method\n\nDo three passes:\n\n1. **Context + risk-map pass (mandatory)** — Start from diff hunks, then read surrounding or full touched files when needed to evaluate maintainability, coupling, naming, and extraction opportunities. Use this context to assess changed behavior, not to run unrelated file-wide audits.\n2. **Blockers pass** — Scan for correctness bugs, security issues, blockchain transaction safety, gas handling issues, data integrity risks, and missing tests for changed behavior. These are `🔴 Bug` comments.\n3. **Maintainability pass** — Scan for code bloat, readability issues, naming problems, pattern violations, hardcoded values, and architecture drift in touched areas. These are `🟡 Issue`, `🔵 Nit`, or `💡 Suggestion` comments.\n\n### Comment Gate\n\nBefore posting any comment, verify all four conditions:\n\n1. **Introduced by this diff** — The issue is introduced or materially worsened by the changes in this PR, not pre-existing.\n2. **Materially impactful** — The issue affects correctness, security, readability, or maintainability in a meaningful way. Not a theoretical concern.\n3. **Concrete fix direction** — You can suggest a specific fix or clear direction. If you can only say \"this seems off\" without a concrete suggestion, do not comment.\n4. **Scope fit** — If the issue is mainly in pre-existing code, the PR must touch the same function/module and fixing it must directly simplify, de-risk, or de-duplicate the new/changed code.\n\nIf any check fails, skip the comment.\nEvery comment must be traceable to changed behavior in this PR and anchored to a right-side line present in `pr-diff.patch`. Prefer added/modified lines; use nearby unchanged hunk lines only when necessary to explain a directly related issue.\n\n**Uncertainty guard:** If you are not certain an issue is real and cannot verify it from the diff and allowed context, do not label it `🔴 Bug`. Downgrade to `🟡 Issue` or `💡 Suggestion`, or skip it entirely.\n\n**Deduplication:** One comment per root cause. If the same pattern repeats across multiple lines, comment on the first occurrence and note \"same pattern at lines X, Y, Z.\" Aim for a maximum of ~10 comments, highest impact first.\n\n## What to Review\n\n### Pass 1: Blockers\n\n#### Correctness\n\n- Logic errors, off-by-one, null/undefined handling, incorrect assumptions, race conditions.\n- Boundary conditions — empty arrays, null inputs, zero values, maximum values.\n- Error handling — swallowed errors, missing error propagation, unhelpful error messages. Do not flag missing error handling for internal code that cannot reasonably fail.\n- Async/await correctness — unhandled promise rejections, missing awaits, race conditions in concurrent operations.\n- Nonce management — verify blockchain nonce allocation and retry logic does not create orphan transactions or nonce gaps.\n\n#### Security\n\n- Injection risks (SQL, command, XSS) when handling user input.\n- Hardcoded secrets — API keys, passwords, private keys, tokens in code. Private keys must never appear in source.\n- Missing input validation at system boundaries (user input, external APIs, RPC responses). Not for internal function calls.\n- Auth bypass, privilege escalation, or missing authorization checks.\n- RPC endpoint exposure — verify no private/paid RPC URLs or API keys are hardcoded in committed code.\n\n#### Blockchain Safety\n\n- Gas handling — verify gas price calculations, multipliers, and buffers are reasonable and consistent across testnet/mainnet.\n- Transaction retry logic — ensure retries don't waste gas, create duplicate transactions, or cause nonce conflicts.\n- Wallet/key management — no hardcoded private keys, proper key isolation between environments.\n- Multi-chain consistency — changes affecting one chain should be verified for impact on other supported chains (NeuroWeb, Gnosis, Base).\n- BigNumber handling — verify arithmetic operations use BigNumber-safe methods, no precision loss from floating point.\n\n#### Tests for Changed Behavior\n\n- New behavior must have corresponding tests covering core functionality and error handling.\n- Bug fixes must include a regression test that would have caught the original bug.\n- Changed behavior must have updated tests reflecting the new expectations.\n- If tests are present but brittle (testing implementation details rather than behavior), flag it.\n\nMissing tests for changed behavior are blockers (`🔴 Bug`) only when the change affects user-facing behavior, API contracts, or data integrity. Missing tests for internal refactors or trivial changes are `🟡 Issue`.\n\n### Pass 2: Maintainability\n\n#### Code Bloat and Unnecessary Complexity\n\n- **Excessive code** — More lines than necessary. Could this be done in fewer lines without sacrificing clarity?\n- **Over-engineering** — Abstractions, helpers, or utilities for one-time operations. Premature generalization.\n- **Dead code** — Unused variables, unreachable branches, commented-out code, leftover debug logging.\n- **Duplicate code** — Same logic repeated instead of extracted. Do not suggest extraction for only 2-3 similar lines unless the repeated logic encodes a correctness invariant across multiple paths.\n\n#### Readability and Naming\n\n- **Confusing variable/function names** — Names that don't describe what the thing is or does. Generic names like `data`, `result`, `item`, `temp`, `val` when a specific name would be clearer.\n- **Misleading names** — Names that suggest different behavior than what the code does.\n- **Inconsistent naming** — Not following conventions in the rest of the codebase.\n- **Long functions** — Functions doing too many things. If you need a comment to explain a section, it should probably be its own function.\n- **Deep nesting** — More than 2-3 levels. Suggest early returns, guard clauses, or extraction.\n- **Unclear control flow** — Complex conditionals that could be simplified or decomposed.\n\n#### Hardcoded Values and Magic Constants\n\nFlag only when the value is:\n\n- **Reused 3+ times** in touched files or the diff — should be a named constant.\n- **Domain-significant** — timeout values, retry counts, gas multipliers, RPC URLs, network message timeouts. Even if used once, these belong in constants or configuration.\n\nDo not flag one-off numeric literals that are self-explanatory in context (e.g., `array.slice(0, 2)`, `Math.round(x * 100) / 100`).\n\n#### Performance (Only Obvious Issues)\n\n- N+1 queries — database queries inside loops.\n- Blocking operations in async contexts — synchronous I/O in async code.\n- Unnecessary work in hot paths — redundant allocations, repeated computations.\n- Memory leaks — Maps/Sets/caches that grow unboundedly without cleanup.\n\n## What NOT to Review\n\n- Formatting or style — ESLint and Prettier handle this.\n- Things that are clearly intentional design choices backed by existing patterns.\n- Pre-existing issues in unchanged code outside the diff.\n- Pre-existing issues in touched files when the PR does not introduce/worsen them.\n- Adding documentation unless a public API is clearly undocumented.\n- Repository-wide or file-wide audits not required by the changed behavior.\n- Test configuration files (cucumber.js, .eslintrc) unless they introduce issues.\n\n## Comment Format\n\nUse severity prefixes:\n\n- `🔴 Bug:` — Correctness error, security issue, blockchain safety issue, data integrity risk. Will cause incorrect behavior.\n- `🟡 Issue:` — Code quality problem that should be fixed. Bloated code, bad naming, pattern violation, missing tests.\n- `🔵 Nit:` — Minor improvement, optional.\n- `💡 Suggestion:` — Alternative approach worth considering.\n\nBe specific, be concise, explain why. One clear sentence with a concrete fix is better than a paragraph of theory.\n\n## Output Format\n\nReturn raw JSON only. No markdown fences, no prose before or after the JSON object. Your output MUST be valid JSON matching the provided output schema. Example:\n\n```json\n{\n  \"summary\": \"This PR improves blockchain error handling but introduces a potential gas waste issue in the retry loop and has leftover debug logging.\",\n  \"comments\": [\n    {\n      \"path\": \"src/modules/blockchain/implementation/web3-service.js\",\n      \"line\": 142,\n      \"body\": \"🔴 Bug: Gas price is bumped on every retry including network errors, which wastes gas. Only bump for nonce conflicts and execution errors. Add a `shouldBumpGas` guard.\"\n    },\n    {\n      \"path\": \"src/commands/protocols/publish/sender/publish-replication-command.js\",\n      \"line\": 58,\n      \"body\": \"🟡 Issue: `console.log` debug statement left in production code. Use `this.logger.debug()` instead or remove it.\"\n    }\n  ]\n}\n```\n\nThe `line` field must refer to the line number in the new version of the file (right side of the diff), and it must be a line that actually appears in the diff hunks. Do not comment on lines outside the diff.\n\n## Summary\n\nWrite a brief (2–4 sentence) overall assessment in the `summary` field covering **only** what this PR's diff changes. Do not mention code, packages, or behavior outside the diff. Lead with blockers if any exist. Mention whether the PR is clean/minimal or has code quality issues. Include one sentence on maintainability direction in touched areas (improved / neutral / worsened, and why). If the PR looks good, say so.\n"
  },
  {
    "path": ".codex/review-schema.json",
    "content": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"summary\": {\n      \"type\": \"string\",\n      \"description\": \"Brief overall assessment of the PR (2-4 sentences)\"\n    },\n    \"comments\": {\n      \"type\": \"array\",\n      \"description\": \"Inline review comments on specific changed lines\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"path\": {\n            \"type\": \"string\",\n            \"description\": \"File path relative to repository root\"\n          },\n          \"line\": {\n            \"type\": \"integer\",\n            \"minimum\": 1,\n            \"description\": \"Line number in the new version of the file (must be within the diff)\"\n          },\n          \"body\": {\n            \"type\": \"string\",\n            \"description\": \"Review comment with severity prefix\"\n          }\n        },\n        \"required\": [\"path\", \"line\", \"body\"],\n        \"additionalProperties\": false\n      }\n    }\n  },\n  \"required\": [\"summary\", \"comments\"],\n  \"additionalProperties\": false\n}\n"
  },
  {
    "path": ".eslintrc.cjs",
    "content": "module.exports = {\n    env: {\n        es6: true,\n        node: true,\n    },\n    extends: ['airbnb/base', 'prettier'],\n    parserOptions: {\n        sourceType: 'module',\n        ecmaVersion: 'latest',\n    },\n    rules: {\n        'linebreak-style': ['error', 'unix'],\n        'class-methods-use-this': 0,\n        'consistent-return': 0,\n        'no-restricted-syntax': 0,\n        'guard-for-in': 0,\n        'no-console': 'warn',\n        'no-continue': 0,\n        'no-underscore-dangle': 0,\n        'import/extensions': 0,\n    },\n    overrides: [\n        {\n            files: ['*.test.js', '*.spec.js'],\n            rules: {\n                'no-unused-expressions': 'off',\n            },\n        },\n        {\n            files: ['*-mock.js', '*.test.js'],\n            rules: {\n                'no-empty-function': 'off',\n                'no-unused-vars': 'off',\n            },\n        },\n    ],\n};\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @branarakic @u-hubar @Mihajlo-Pavlovic\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_v8.md",
    "content": "---\nname: Bug report for V8 ot-node\nabout: Create an issue report\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Issue description\n\n## Expected behavior\n\n## Actual behavior\n\n## Steps to reproduce the problem\n\n  1.\n  2.\n  3.\n\n## Specifications\n\n  - Node version:\n  - Platform:\n  - Node wallet:\n  - Node libp2p identity:\n\n## Contact details\n  - Email:\n\n## Error logs\n\n\n## Disclaimer\n\nPlease be aware that the issue reported on a public repository allows everyone to see your node logs, node details, and contact details. If you have any sensitive information, feel free to share it by sending an email to [tech@origin-trail.com](tech@origin-trail.com).\n"
  },
  {
    "path": ".github/actions/setup/action.yml",
    "content": "name: setup\n\nruns:\n  using: composite\n  steps:\n    - name: Setup NodeJS\n      id: nodejs\n      uses: actions/setup-node@v3\n      with:\n        node-version: 20.x\n        cache: npm\n\n    - name: Cache node modules\n      id: cache-node-modules\n      uses: actions/cache@v3\n      with:\n        path: '**/node_modules'\n        key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}\n        restore-keys: |\n            ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}\n            ${{ runner.os }}-node-modules-\n\n    - name: Cache Blazegraph\n      id: cache-blazegraph\n      uses: actions/cache@v3\n      with:\n        path: blazegraph.jar\n        key: ${{ runner.os }}-blazegraph-${{ hashFiles('blazegraph.jar') }}\n        restore-keys: ${{ runner.os }}-blazegraph-\n\n    - if: steps.cache-node-modules.outputs.cache-hit != 'true'\n      name: Install dependencies & compile contracts\n      shell: bash\n      run: |\n        npm install\n        npm install --save-dev rollup@4.40.0\n        npm explore dkg-evm-module -- npm run compile\n    \n    - if: steps.cache-blazegraph.outputs.cache-hit != 'true'\n      name: Download Blazegraph\n      shell: bash\n      run: wget https://github.com/blazegraph/database/releases/latest/download/blazegraph.jar\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "# Description\n\nPlease include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.\n\nFixes # (issue)\n\n## Type of change\n\nPlease delete options that are not relevant.\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n- [ ] This change requires a documentation update\n\n# How Has This Been Tested?\n\nPlease describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration\n\n- [ ] Test A\n- [ ] Test B\n\n# Checklist:\n\n- [ ] My code follows the style guidelines of this project\n- [ ] I have performed a self-review of my own code\n- [ ] I have commented my code, particularly in hard-to-understand areas\n- [ ] I have made corresponding changes to the documentation\n- [ ] My changes generate no new warnings\n- [ ] I have added tests that prove my fix is effective or that my feature works\n- [ ] New and existing unit tests pass locally with my changes\n- [ ] Any dependent changes have been merged and published in downstream modules\n"
  },
  {
    "path": ".github/release-drafter-template.yml",
    "content": "name-template: 'OriginTrail Release $NEXT_PATCH_VERSION'\ntag-template: \"$NEXT_PATCH_VERSION\"\nversion-template: \"v$MAJOR.$MINOR.$PATCH\"\ncategories:\n  - title: '🚀 Features'\n    labels:\n      - 'enhancement'\n  - title: '🐛 Bug Fixes'\n    labels:\n      - 'bug'\n  - title: '🧰 Maintenance'\n    labels:\n      - 'internal process'\n  - title: '⚠️ Breaking changes'\n    labels:\n      - 'breaking change'\nchange-template: '- $TITLE (#$NUMBER)'\ntemplate: |\n  # Changes\n\n  $CHANGES"
  },
  {
    "path": ".github/workflows/check-package-lock.yml",
    "content": "name: Check Package Lock File\n\npermissions:\n  contents: read\n\nconcurrency:\n  group: check-package-lock-${{ github.ref }}\n  cancel-in-progress: true\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - \"**\"\n\njobs:\n  verify-package-lock:\n    name: Verify package-lock.json\n    runs-on: ubuntu-latest\n    timeout-minutes: 5\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Check if package.json dependencies were changed\n        id: check-changes\n        run: |\n          if [ \"${{ github.event_name }}\" = \"pull_request\" ]; then\n            BASE_SHA=\"${{ github.event.pull_request.base.sha }}\"\n          else\n            BASE_SHA=\"${{ github.event.before }}\"\n          fi\n\n          if ! git diff --name-only \"$BASE_SHA\" HEAD | grep -q '^package\\.json$'; then\n            echo \"package_json_changed=false\" >> \"$GITHUB_OUTPUT\"\n            echo \"package.json was NOT changed, skipping lock file validation\"\n            exit 0\n          fi\n\n          echo \"package_json_changed=true\" >> \"$GITHUB_OUTPUT\"\n          echo \"package.json was changed, will validate lock file\"\n\n      - name: Check if package-lock.json exists\n        run: |\n          if [ ! -f \"package-lock.json\" ]; then\n            echo \"ERROR: package-lock.json file is missing from the repository\"\n            echo \"This file is required to ensure consistent dependency versions across all environments\"\n            echo \"Please ensure package-lock.json is committed with your changes\"\n            exit 1\n          fi\n          echo \"SUCCESS: package-lock.json file is present\"\n\n      - name: Verify package-lock.json is not empty\n        run: |\n          if [ ! -s \"package-lock.json\" ]; then\n            echo \"ERROR: package-lock.json file exists but is empty\"\n            echo \"Please run 'npm install' to regenerate the lock file\"\n            exit 1\n          fi\n          echo \"SUCCESS: package-lock.json file is valid and not empty\"\n\n      - name: Setup Node.js\n        if: steps.check-changes.outputs.package_json_changed == 'true'\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n\n      - name: Validate package-lock.json is in sync\n        if: steps.check-changes.outputs.package_json_changed == 'true'\n        run: npm ci --dry-run --ignore-scripts\n"
  },
  {
    "path": ".github/workflows/checks.yml",
    "content": "name: checks\n\non:\n  pull_request:\n    types: [opened, reopened, synchronize]\n  push:\n    branches:\n      - '**'\n\npermissions:\n  contents: read\n\nenv:\n  REPOSITORY_PASSWORD: password\n  JWT_SECRET: aTx13FzDG+85j9b5s2G7IBEc5SJNJZZLPLe7RF8hu1xKgRKj46YFRx/z7fJi7iF2NnL7SHcxTzq7TySuPKWkdg/AYKEMD2p1I++qPYFHqg8KQeLArGjCYiqtf43i1Fgtya8z9qJXyegogMz/jYori2BJ8v6b4K3GkAw3XxiO7VaaEYktOp8qsRDcN3b+bITMZqztDvZdWp4EnViGjoES7fRFhKm/d/2C8URnQyGm6xgTR3xTfAjy7+milGmoPA0KU0nu+GsZIhOfeVc9Z2nfxOK/1JQykpjeBhNDYTOr31yW/xdvoW0Kq0PZ6JmM+yezLoyQXcYjavZ+X7cXjbREQg==\n\nconcurrency:\n  group: checks-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up environment\n        uses: ./.github/actions/setup\n\n      - name: Run linter\n        run: npm run lint\n\n  bdd-smoke:\n    if: github.event_name == 'push'\n    runs-on: ubuntu-latest\n    services:\n      mysql:\n        image: mysql:5.7\n        env:\n          MYSQL_DATABASE: operationaldb\n          MYSQL_USER: node\n          MYSQL_PASSWORD: password\n          MYSQL_ROOT_PASSWORD: password\n        ports:\n          - 3306:3306\n        options: --health-cmd=\"mysqladmin ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n      redis:\n        image: redis:7\n        ports:\n          - 6379:6379\n        options: --health-cmd=\"redis-cli ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up environment\n        uses: ./.github/actions/setup\n\n      - name: Start Blazegraph\n        run: /usr/bin/java -Djava.awt.headless=true -jar blazegraph.jar &\n\n      - name: Wait for Blazegraph\n        run: |\n          for i in $(seq 1 30); do\n            if curl -sf http://localhost:9999/blazegraph/status > /dev/null; then\n              echo \"Blazegraph is ready\"\n              exit 0\n            fi\n            sleep 2\n          done\n          echo \"Blazegraph did not start in time\"\n          exit 1\n\n      - name: Run BDD smoke tests\n        run: |\n          npx cucumber-js \\\n            --config cucumber.js \\\n            --tags \"@smoke\" \\\n            --format progress \\\n            test/bdd/ \\\n            --import test/bdd/steps/ \\\n            --exit\n\n      - name: Upload log files\n        if: '!cancelled()'\n        uses: actions/upload-artifact@v4\n        with:\n          name: bdd-smoke-logs\n          path: ./test/bdd/log/\n\n  bdd-tests:\n    if: github.event_name == 'pull_request'\n    runs-on: ubuntu-latest\n    services:\n      mysql:\n        image: mysql:5.7\n        env:\n          MYSQL_DATABASE: operationaldb\n          MYSQL_USER: node\n          MYSQL_PASSWORD: password\n          MYSQL_ROOT_PASSWORD: password\n        ports:\n          - 3306:3306\n        options: --health-cmd=\"mysqladmin ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n      redis:\n        image: redis:7\n        ports:\n          - 6379:6379\n        options: --health-cmd=\"redis-cli ping\" --health-interval=10s --health-timeout=5s --health-retries=3\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up environment\n        uses: ./.github/actions/setup\n\n      - name: Start Blazegraph\n        run: /usr/bin/java -Djava.awt.headless=true -jar blazegraph.jar &\n\n      - name: Wait for Blazegraph\n        run: |\n          for i in $(seq 1 30); do\n            if curl -sf http://localhost:9999/blazegraph/status > /dev/null; then\n              echo \"Blazegraph is ready\"\n              exit 0\n            fi\n            sleep 2\n          done\n          echo \"Blazegraph did not start in time\"\n          exit 1\n\n      - name: Run full BDD tests\n        run: |\n          npx cucumber-js \\\n            --config cucumber.js \\\n            --tags \"not @ignore\" \\\n            --format progress \\\n            test/bdd/ \\\n            --import test/bdd/steps/ \\\n            --exit\n\n      - name: Upload log files\n        if: '!cancelled()'\n        uses: actions/upload-artifact@v4\n        with:\n          name: bdd-tests-logs\n          path: ./test/bdd/log/\n"
  },
  {
    "path": ".github/workflows/codex-review.yml",
    "content": "name: Codex PR Review\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened]\n\nconcurrency:\n  group: codex-review-${{ github.event.pull_request.number }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  review:\n    name: Codex Review\n    runs-on: ubuntu-latest\n    timeout-minutes: 15\n    # Skip fork PRs — they cannot access repository secrets\n    if: github.event.pull_request.head.repo.full_name == github.repository\n\n    steps:\n      - name: Checkout PR merge commit\n        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4\n        with:\n          ref: refs/pull/${{ github.event.pull_request.number }}/merge\n          fetch-depth: 0\n\n      - name: Generate PR diff\n        run: git diff ${{ github.event.pull_request.base.sha }}...HEAD > pr-diff.patch\n\n      - name: Allow unprivileged user namespaces for bubblewrap sandbox\n        run: |\n          if sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns 2>/dev/null; then\n            sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0\n          fi\n\n      - name: Run Codex review\n        id: codex\n        uses: openai/codex-action@f5c0ca71642badb34c1e66321d8d85685a0fa3dc # v1\n        with:\n          openai-api-key: ${{ secrets.OPENAI_API_KEY }}\n          prompt-file: .codex/review-prompt.md\n          output-schema-file: .codex/review-schema.json\n          effort: high\n          sandbox: read-only\n\n      - name: Post PR review with inline comments\n        uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7\n        env:\n          REVIEW_JSON: ${{ steps.codex.outputs.final-message }}\n        with:\n          script: |\n            let review;\n            try {\n              review = JSON.parse(process.env.REVIEW_JSON);\n            } catch (e) {\n              console.error('Failed to parse Codex output:', e.message);\n              console.error('Raw output:', process.env.REVIEW_JSON?.slice(0, 500));\n              await github.rest.pulls.createReview({\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                pull_number: context.issue.number,\n                body: '⚠️ Codex review failed to produce valid JSON output. Check the [workflow logs](' +\n                  `${process.env.GITHUB_SERVER_URL}/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}) for details.`,\n                event: 'COMMENT',\n                comments: [],\n              });\n              return;\n            }\n\n            // Fetch all changed files (paginated for large PRs)\n            const files = await github.paginate(\n              github.rest.pulls.listFiles,\n              {\n                owner: context.repo.owner,\n                repo: context.repo.repo,\n                pull_number: context.issue.number,\n                per_page: 100,\n              }\n            );\n\n            // Build set of valid (path:line) pairs from right-side diff hunk lines\n            // (added + context). This keeps comments bound to changed areas.\n            const validLines = new Set();\n            for (const file of files) {\n              // Skip binary/large/truncated files with no patch\n              if (!file.patch) continue;\n\n              const lines = file.patch.split('\\n');\n              let currentLine = 0;\n              for (const line of lines) {\n                const hunkMatch = line.match(/^@@ -\\d+(?:,\\d+)? \\+(\\d+)/);\n                if (hunkMatch) {\n                  currentLine = parseInt(hunkMatch[1], 10);\n                  continue;\n                }\n                // Added lines are valid comment targets\n                if (line.startsWith('+')) {\n                  validLines.add(`${file.filename}:${currentLine}`);\n                  currentLine++;\n                  continue;\n                }\n                // Deleted lines don't exist in the new file\n                if (line.startsWith('-')) continue;\n                // Ignore hunk metadata lines\n                if (line.startsWith('\\\\')) continue;\n                // Context lines on the right side are also valid targets\n                validLines.add(`${file.filename}:${currentLine}`);\n                currentLine++;\n              }\n            }\n\n            // Partition comments into valid (on right-side diff lines) and dropped\n            const comments = Array.isArray(review.comments) ? review.comments : [];\n            const validComments = [];\n            const droppedComments = [];\n\n            for (const comment of comments) {\n              const key = `${comment.path}:${comment.line}`;\n              if (validLines.has(key)) {\n                validComments.push({\n                  path: comment.path,\n                  line: comment.line,\n                  body: comment.body,\n                  side: 'RIGHT',\n                });\n              } else {\n                droppedComments.push(comment);\n              }\n            }\n\n            // Build review body from summary only.\n            // Intentionally do NOT publish out-of-diff comments.\n            let body = review.summary || 'Codex review complete.';\n\n            // Post the review\n            await github.rest.pulls.createReview({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              pull_number: context.issue.number,\n              body,\n              event: 'COMMENT',\n              comments: validComments,\n            });\n\n            console.log(`Review posted: ${validComments.length} inline comments, ${droppedComments.length} dropped out-of-diff comments`);\n"
  },
  {
    "path": ".github/workflows/release-drafter-config.yml",
    "content": "name: release-drafter\n\non:\n  push:\n    branches:\n      - develop\n\njobs:\n  update_release_draft:\n    runs-on: ubuntu-latest\n    steps:\n      # Drafts your next Release notes as Pull Requests are merged into \"master\"\n      - uses: release-drafter/release-drafter@v6\n        with:\n          # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml\n          config-name: release-drafter-template.yml\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/update-cache.yml",
    "content": "name: update-cache\n\non:\n  push:\n    branches:\n      - v8/develop\n\nconcurrency:\n  group: update-cache-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build-cache:\n    name: Build Cache\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up environment\n        uses: ./.github/actions/setup\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.idea\n.origintrail_noderc\n.*_origintrail_noderc.json\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# TypeScript v1 declaration files\ntypings/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n\n# Next.js build output\n.next\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and *not* Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n.DS_Store\n\n# Test data folders\ntest-data*\n\n# Data folders\ndata*\n\n# VS code files\n.vscode/launch.json\n\n# KAs Distribution Simulation Script Plots\ntools/knowledge-assets-distribution-simulation/plots/**/*jpg\n\nnode_modules\n.env\n\n# Hardhat files\n/cache\n/artifacts\n\n# TypeChain files\n/typechain\n/typechain-types\n\n# solidity-coverage files\n/coverage\n/coverage.json\n\n# Hardhat Ignition default folder for deployments against a local node\nignition/deployments/chain-31337\n\n# Redis\ndump.rdb\n\n# Blazegraph journal files\n*.jnl\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run lint-staged\n"
  },
  {
    "path": ".lintstagedrc.json",
    "content": "{\n  \"*.{js, json}\": [\"prettier --write\", \"eslint\"]\n}\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": true,\n  \"printWidth\": 100,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"arrowParens\": \"always\",\n  \"tabWidth\": 4,\n  \"bracketSpacing\": true\n}\n"
  },
  {
    "path": "Alpine.Dockerfile",
    "content": "FROM node:14-alpine3.15\n\nLABEL maintainer=\"OriginTrail\"\nENV NODE_ENV=testnet\n\n#Install Papertrail\nRUN wget https://github.com/papertrail/remote_syslog2/releases/download/v0.20/remote_syslog_linux_amd64.tar.gz\nRUN tar xzf ./remote_syslog_linux_amd64.tar.gz && cd remote_syslog && cp ./remote_syslog /usr/local/bin\n\nCOPY config/papertrail.yml /etc/log_files.yml\n\n#Install git & forever\nRUN npm install forever -g\nRUN apk add git\n\nWORKDIR /ot-node\n\nCOPY . .\n\n#Install nppm\nRUN npm install\n\n\n\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\ntech@origin-trail.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\n## Rules\n\nThere are a few basic ground-rules for contributors (including the maintainer(s) of the project):\n\n- **No `--force` pushes** or modifying the Git history in any way. If you need to rebase, ensure you do it in your own repo.\n- **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.\n\n### Reviewing pull requests\n\nWhen reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in:\n\n- Buggy behavior.\n- Undue maintenance burden.\n- Breaking with house coding style.\n- Pessimization (i.e. reduction of speed as measured in the projects benchmarks).\n- Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on).\n- Uselessness (i.e. it does not strictly add a feature or fix a known issue).\n\n### Reviews may not be used as an effective veto for a PR because\n\n- There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix.\n- It does not fit well with some other contributors' longer-term vision for the project.\n\n## Releases\n\nDeclaring formal releases remains the prerogative of the project maintainer(s).\n\n## Changes to this arrangement\n\nThis is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.\n\n## Heritage\n\nThese contributing guidelines are modified from the \"OPEN Open Source Project\" guidelines for the Level project: <https://github.com/Level/community/blob/master/CONTRIBUTING.md>\n"
  },
  {
    "path": "Debian.Dockerfile",
    "content": "#base image\nFROM node:14.18.3-bullseye\n\nMAINTAINER OriginTrail\nLABEL maintainer=\"OriginTrail\"\nENV NODE_ENV=testnet\n\n\n#Mysql-server installation\nARG DEBIAN_FRONTEND=noninteractive\nARG PASSWORD=password\nRUN apt-get update\nRUN apt-get install -y lsb-release\nRUN apt-get install -y wget gnupg curl\nRUN curl -LO https://dev.mysql.com/get/mysql-apt-config_0.8.20-1_all.deb\nRUN dpkg -i ./mysql-apt-config_0.8.20-1_all.deb\nRUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 467B942D3A79BD29\n\n\nRUN { \\\n     echo mysql-server mysql-server/root_password password $PASSWORD ''; \\\n     echo mysql-server mysql-server/root_password_again password $PASSWORD ''; \\\n} | debconf-set-selections \\\n    && apt-get update && apt-get install -y default-mysql-server default-mysql-server-core\n\n\n\nRUN apt-get -qq -y install git\nRUN apt-get -qq -y install make python\n\n#Install Papertrail\nRUN wget https://github.com/papertrail/remote_syslog2/releases/download/v0.20/remote_syslog_linux_amd64.tar.gz\nRUN tar xzf ./remote_syslog_linux_amd64.tar.gz && cd remote_syslog && cp ./remote_syslog /usr/local/bin\nCOPY config/papertrail.yml /etc/log_files.yml\n\n\n\n\n\n\n#Install forever\nRUN npm install forever -g\n\n\n\n\nWORKDIR /ot-node\n\nCOPY . .\n\n#Install nppm\nRUN npm install\n\n#Mysql intialization\nRUN service mariadb start && mysql -u root  -e \"CREATE DATABASE operationaldb /*\\!40100 DEFAULT CHARACTER SET utf8 */; SET PASSWORD FOR root@localhost = PASSWORD(''); FLUSH PRIVILEGES;\"\n\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2018 OriginTrail\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "<a name=\"readme-top\"></a>\n\n---\n\n<br />\n<div align=\"center\">\n  <a href=\"https://github.com/OriginTrail/ot-node\">\n    <img src=\"images/banner.gif\" alt=\"OriginTrail Node Banner\">\n  </a>\n\n  <h3 align=\"center\"><b>OT-Node</b></h3>\n\n  <p align=\"center\">\n    </br>\n    <a href=\"https://docs.origintrail.io/\">OriginTrail Docs</a>\n    ·\n    <a href=\"https://github.com/OriginTrail/ot-node/issues\">Report Bug</a>\n    ·\n    <a href=\"https://github.com/OriginTrail/ot-node/issues\">Request Feature</a>\n  </p>\n</div>\n\n</br>\n\n<details open>\n  <summary>\n    <b>Table of Contents</b>\n  </summary>\n  <ol>\n    <li>\n      <a href=\"#📚-about-the-project\">📚 About The Project</a>\n      <ul>\n        <li><a href=\"#what-is-the-decentralized-knowledge-graph\">What is the Decentralized Knowledge Graph?</a></li>\n        <li><a href=\"#the-origintrail-dkg-architecture\">The OriginTrail DKG Architecture</a></li>\n        <li><a href=\"#what-is-a-knowledge-asset\">What is a Knowledge Asset?</a></li>\n      </ul>\n    </li>\n    <li>\n      <a href=\"#🚀-getting-started\">🚀 Getting Started</a>\n      <ul>\n        <li><a href=\"#prerequisites\">Prerequisites</a></li>\n        <li><a href=\"#local-network-setup\">Local Network Setup</a></li>\n        <li><a href=\"#dkg-node-setup\">DKG Node Setup</a></li>\n        <li><a href=\"#build-on-dkg\">Build on DKG</a></li>\n      </ul>\n    </li>\n    <li><a href=\"#📄-license\">📄 License</a></li>\n    <li><a href=\"#🤝-contributing\">🤝 Contributing</a></li>\n    <li><a href=\"#📰-social-media\">📰 Social Media</a></li>\n  </ol>\n</details>\n\n---\n\n<br/>\n\n## 📚 About The Project\n\n<details open>\n<summary>\n\n### **What is the Decentralized Knowledge Graph?**\n\n</summary>\n\n<br/>\n\n<div align=\"center\">\n    <img src=\"images/nodes.png\" alt=\"Knowledge Asset\" width=\"200\">\n</div>\n\nOriginTrail Decentralized Knowledge Graph (DKG), hosted on the OriginTrail Decentralized Network (ODN) as trusted knowledge infrastructure, is shared global Knowledge Graph of Knowledge Assets. Running on the basis of the permissionless multi-chain OriginTrail protocol, it combines blockchains and knowledge graph technology to enable trusted AI applications based on key W3C standards.\n\n</details>\n\n<details open>\n<summary>\n\n### **The OriginTrail DKG Architecture**\n\n</summary>\n\n<br/>\n\nThe OriginTrail tech stack is a three layer structure, consisting of the multi-chain consensus layer (OriginTrail layer 1, running on multiple blockchains), the Decentralized Knowledge Graph layer (OriginTrail Layer 2, hosted on the ODN) and Trusted Knowledge applications in the application layer.\n\n<div align=\"center\">\n    <img src=\"images/dkg-architecture.png\" alt=\"DKG Architecture\" width=\"400\">\n</div>\n\nFurther, the architecture differentiates between **the public, replicated knowledge graph** shared by all network nodes according to the protocol, and **private Knowledge graphs** hosted separately by each of the OriginTrail nodes.\n\n**Anyone can run an OriginTrail node and become part of the ODN, contributing to the network capacity and hosting the OriginTrail DKG. The OriginTrail node is the ultimate data service for data and knowledge intensive Web3 applications and is used as the key backbone for trusted AI applications (see https://chatdkg.ai)**\n\n</details>\n\n<details open>\n<summary>\n\n### **What is a Knowledge Asset?**\n\n</summary>\n\n<br/>\n\n<div align=\"center\">\n    <img src=\"images/ka.png\" alt=\"Knowledge Asset\" width=\"200\">\n</div>\n\n**Knowledge Asset is the new, AI‑ready resource for the Internet**\n\nKnowledge Assets are verifiable containers of structured knowledge that live on the OriginTrail DKG and provide:\n\n-   **Discoverability - UAL is the new URL**. Uniform Asset Locators (UALs, based on the W3C Decentralized Identifiers) are a new Web3 knowledge identifier (extensions of the Uniform Resource Locators - URLs) which identify a specific piece of knowledge and make it easy to find and connect with other Knowledge Assets.\n-   **Ownership - NFTs enable ownership**. Each Knowledge Asset contains an NFT token that enables ownership, knowledge asset administration and market mechanisms.\n-   **Verifiability - On-chain information origin and verifiable trail**. The blockchain tech increases trust, security, transparency, and the traceability of information.\n\nBy their nature, Knowledge Assets are semantic resources (following the W3C Semantic Web set of standards), and through their symbolic representations inherently AI ready. See more at https://chatdkg.ai\n<br/>\n\n**Discover Knowledge Assets with the DKG Explorer:**\n\n<div align=\"center\">\n    <table>\n        <tr>\n            <td align=\"center\">\n                <a href=\"https://dkg.origintrail.io/explore?ual=did:dkg:otp/0x5cac41237127f94c2d21dae0b14bfefa99880630/309100\">\n                  <img src=\"images/knowledge-assets-graph1.svg\" width=\"300\" alt=\"Knowledge Assets Graph 1\">\n                </a>\n                <br><b>Supply Chains</b>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://dkg.origintrail.io/explore?ual=did:dkg:otp/0x5cac41237127f94c2d21dae0b14bfefa99880630/309285\">\n                  <img src=\"images/knowledge-assets-graph2.svg\" width=\"300\" alt=\"Knowledge Assets Graph 2\">\n                </a>\n                <br><b>Construction</b>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://dkg.origintrail.io/explore?ual=did:dkg:otp/0x5cac41237127f94c2d21dae0b14bfefa99880630/309222\">\n                  <img src=\"images/knowledge-assets-graph3.svg\" width=\"300\" alt=\"Knowledge Assets Graph 3\">\n                </a>\n                <br><b>Life sciences and healthcare</b>\n            </td>\n            <td align=\"center\">\n                <a href=\"https://dkg.origintrail.io/explore?ual=did:dkg:otp/0x5cac41237127f94c2d21dae0b14bfefa99880630/308028\">\n                  <img src=\"images/knowledge-assets-graph4.svg\" width=\"300\" alt=\"Knowledge Assets Graph 3\">\n                </a>\n                <br><b>Metaverse</b>\n            </td>\n        </tr>\n    </table>\n</div>\n\n</details>\n\n<p align=\"right\">(<a href=\"#readme-top\">back to top</a>)</p>\n<br/>\n\n## 🚀 Getting Started\n\n---\n\n### Prerequisites\n\n<br/>\n\n-   **Node.js** 20.18\n-   **npm** 10.8.2\n\n---\n\n<br/>\n\n### Local Network Setup\n\n<br/>\n\nFirst, clone the repo:\n\n```bash\ngit clone https://github.com/OriginTrail/ot-node.git\ncd ot-node\n```\n\nSwitch the branch to `v8/develop`:\n\n```bash\ngit checkout v8/develop\n```\n\nInstall dependencies using `npm`:\n\n```bash\nnpm install\n```\n\nCreate the .env file inside the \"ot-node\" directory:\n\n```bash\nnano .env\n```\n\nand paste the following content inside (save and close):\n\n```bash\nNODE_ENV=development\nRPC_ENDPOINT_BC1=http://localhost:8545\nRPC_ENDPOINT_BC2=http://localhost:9545\nREPOSITORY_PASSWORD=\n```\n\nRun the Triple Store.\n\nTo use default Triple Store (`blazegraph`), download the exec file and run it with the following command in the separate process:\n\n```bash\njava -server -Xmx6g -jar blazegraph.jar\n```\n\nIt's highly recommended to use a larger heap size (6GB or 8GB), as the DKG node will require a lot of memory.\n\nEnsure your MySQL instance is running on port 3306 with the password matching REPOSITORY_PASSWORD in your .env file. Additionally, set up Redis on its default port 6379. Both are required for the nodes to start properly.\n\nThen, depending on the OS, use one of the scripts in order to run the local network with provided number of nodes (minimal amount of nodes should be 6):\n\n**MacOS**\n\n```bash\nbash ./tools/local-network-setup/setup-macos-environment.sh --nodes=6\n```\n\n**Linux**\n\n```bash\n./tools/local-network-setup/setup-linux-environment.sh --nodes=6\n```\n\n---\n\n<br/>\n\n### DKG Node Setup\n\n<br/>\n\nIn order to run a DKG node on the **V8 Testnet**, please read the official documentation: https://docs.origintrail.io/dkg-v8-upcoming-version/run-a-v8-core-node-on-testnet\n\n---\n\n<br/>\n\n### Build on DKG\n\n<br/>\n\nThe OriginTrail SDKs are client libraries for your applications, used to interact and connect with the OriginTrail Decentralized Knowledge Graph.\nFrom an architectural standpoint, the SDK libraries are application interfaces into the DKG, enabling you to create and manage Knowledge Assets through your apps, as well as perform network queries (such as search, or SPARQL queries), as illustrated below.\n\n<div align=\"center\">\n    <img src=\"images/sdk.png\" alt=\"SDK\" width=\"200\">\n</div>\n\nThe OriginTrail SDK libraries are being built in various languages by the team and the community, as listed below:\n\n-   dkg.js - V8 JavaScript SDK implementation\n    -   [Github repository](https://github.com/OriginTrail/dkg.js/tree/v8/develop)\n    -   [Documentation](https://docs.origintrail.io/dkg-v8-upcoming-version/v8-dkg-sdk/dkg-v8-js-client)\n-   dkg.py - V8 Python SDK implementation\n    -   [Github repository](https://github.com/OriginTrail/dkg.py/tree/v8/develop)\n    -   [Documentation](https://docs.origintrail.io/dkg-v8-upcoming-version/v8-dkg-sdk/dkg-v8-py-client)\n\n---\n\n<br/>\n<p align=\"right\">(<a href=\"#readme-top\">back to top</a>)</p>\n\n## 📄 License\n\nDistributed under the Apache-2.0 License. See `LICENSE` file for more information.\n\n<br/>\n<p align=\"right\">(<a href=\"#readme-top\">back to top</a>)</p>\n\n## 🤝 Contributing\n\nContributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.\n\nIf you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag \"enhancement\".\nDon't forget to give the project a star! Thanks again!\n\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n<br/>\n<p align=\"right\">(<a href=\"#readme-top\">back to top</a>)</p>\n\n## 📰 Social Media\n\n<br/>\n\n<div align=\"center\">\n  <a href=\"https://medium.com/origintrail\">\n    <img src=\"images/icons/medium.svg\" alt=\"Medium Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://t.me/origintrail\">\n    <img src=\"images/icons/telegram.svg\" alt=\"Telegram Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://x.com/origin_trail\">\n    <img src=\"images/icons/x.svg\" alt=\"X Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://www.youtube.com/c/origintrail\">\n    <img src=\"images/icons/youtube.svg\" alt=\"YouTube Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://www.linkedin.com/company/origintrail/\">\n    <img src=\"images/icons/linkedin.svg\" alt=\"LinkedIn Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://discord.gg/cCRPzzmnNT\">\n    <img src=\"images/icons/discord.svg\" alt=\"Discord Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://www.reddit.com/r/OriginTrail/\">\n    <img src=\"images/icons/reddit.svg\" alt=\"Reddit Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n  <a href=\"https://coinmarketcap.com/currencies/origintrail/\">\n    <img src=\"images/icons/coinmarketcap.svg\" alt=\"Coinmarketcap Badge\" width=\"30\" style=\"margin-right: 10px\"/>\n  </a>\n</div>\n\n---\n"
  },
  {
    "path": "Ubuntu.Dockerfile",
    "content": "#base image\nFROM ubuntu:20.04\n\nMAINTAINER OriginTrail\nLABEL maintainer=\"OriginTrail\"\nENV NODE_ENV=testnet\n\n#Install git, nodejs, mysql, python\nRUN apt-get -qq update && apt-get -qq -y install curl\nRUN curl -sL https://deb.nodesource.com/setup_14.x |  bash -\nRUN apt-get -qq update\nRUN apt-get -qq -y install wget apt-transport-https\nRUN apt-get -qq -y install git nodejs\nRUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y --no-install-recommends mysql-server\nRUN apt-get -qq -y install unzip nano\nRUN apt-get -qq -y install make python\n\n#Install Papertrail\nRUN wget https://github.com/papertrail/remote_syslog2/releases/download/v0.20/remote_syslog_linux_amd64.tar.gz\nRUN tar xzf ./remote_syslog_linux_amd64.tar.gz && cd remote_syslog && cp ./remote_syslog /usr/local/bin\nCOPY config/papertrail.yml /etc/log_files.yml\n\n\n\n#Install forever\nRUN npm install -g forever\n\n\nWORKDIR /ot-node\n\nCOPY . .\n\n\n#Install npm\nRUN npm install\n\n\n#Intialize mysql\nRUN usermod -d /var/lib/mysql/ mysql\nRUN echo \"disable_log_bin\" >> /etc/mysql/mysql.conf.d/mysqld.cnf\nRUN service mysql start && mysql -u root  -e \"CREATE DATABASE operationaldb /*\\!40100 DEFAULT CHARACTER SET utf8 */; update mysql.user set plugin = 'mysql_native_password' where User='root'/*\\!40100 DEFAULT CHARACTER SET utf8 */; flush privileges;\"\n\n"
  },
  {
    "path": "bin/darwin/arm64/.gitkeep",
    "content": ""
  },
  {
    "path": "bin/darwin/x64/.gitkeep",
    "content": ""
  },
  {
    "path": "bin/linux/arm64/.gitkeep",
    "content": ""
  },
  {
    "path": "bin/linux/x64/.gitkeep",
    "content": ""
  },
  {
    "path": "bin/win32/x64/.gitkeep",
    "content": ""
  },
  {
    "path": "blazegraph-migration/README.md",
    "content": "The migration is manual and split into two scripts: export and import, which you must run yourself. Your node will be offline during export (several hours), but usable during import. Core nodes won’t be affected staking-wise. Import time varies based on data and hardware and can take hours to days.\n\nAfter finishing both export and import processes, the check_quad_num.sh should be ran. It will return info on the outcome of the migration.\n\nWe recommend migrating before the 8.0.6 release, as it will add more data and increase future migration time. The process removes blazegraph.jnl and rebuilds it from exported DKG and paranet repositories — so if you have custom data, review the script and back up your journal file first.\n\nBefore running the migration, make sure the blazegraph.jnl you are migrating is in the ot-node directory and that blazegraph is running.\n\nExport script (to export dkg and paranet namespaces):\n\n```bash\nnohup ./current/blazegraph-migration/export.sh /path_to_ot_node/ot-node dkg $(curl -s http://localhost:9999/blazegraph/namespace | grep -oP '<Namespace[^>]*>\\K[^<]+' | grep '^paranet-') | tee export_migration.log &\n```\n\nImport script:\n\n```bash\n./current/blazegraph-migration/import.sh\n```\n\nCheck quad number:\n\n```bash\n./current/blazegraph-migration/check_quad_num.sh\n```\n"
  },
  {
    "path": "blazegraph-migration/check_quad_num.sh",
    "content": "#!/bin/bash\n\nget_mysql_password() {\n    local base_dir=$(dirname \"$0\")\n    grep ^REPOSITORY_PASSWORD= \"./current/.env\" | cut -d '=' -f2\n}\n\nget_inserted_triples_count() {\n    local repo_pw=$1\n    mysql -u root -p\"$repo_pw\" operationaldb -e \"SELECT count FROM triples_insert_count WHERE id = 1;\" | tail -n 1\n}\n\nget_blazegraph_count() {\n    local namespace=$1\n    local BLAZEGRAPH_URL=\"http://localhost:9999/blazegraph/namespace/$namespace/sparql\"\n    QUAD_COUNT=$(curl -s -X POST \"$BLAZEGRAPH_URL\" \\\n      -H \"Accept: text/tab-separated-values\" \\\n      --data-urlencode 'query=SELECT (COUNT(*) AS ?total) WHERE { GRAPH ?g { ?s ?p ?o } }' \\\n      | tail -n 1)\n\n    if ! [[ \"$QUAD_COUNT\" =~ ^[0-9]+$ ]]; then\n        echo \"Error: Failed to get valid count from Blazegraph\" >&2\n        return 1\n    fi\n\n    echo \"$QUAD_COUNT\"\n    return 0\n}\n\nget_old_count() {\n    local old_count_file=$1\n    if [ ! -f \"$old_count_file\" ]; then\n        echo \"Error: $old_count_file not found\" >&2\n        return 1\n    fi\n    cat \"$old_count_file\"\n}\n\nget_uninserted_count() {\n    local namespace=$1\n    local chunks_dir=\"${namespace}/chunks\"\n\n    if [ ! -d \"$chunks_dir\" ]; then\n        echo \"Error: Chunks directory not found at $chunks_dir\" >&2\n        return 1\n    fi\n\n    local total_lines=0\n    for chunk_file in \"$chunks_dir\"/chunk*; do\n        if [ -f \"$chunk_file\" ]; then\n            local lines=$(wc -l < \"$chunk_file\")\n            total_lines=$((total_lines + lines))\n        fi\n    done\n\n    echo \"$total_lines\"\n    return 0\n}\n\ncheck_quad_count() {\n    local namespace=$1\n    local old_count_file=\"OLD_QUAD_COUNT_${namespace}.txt\"\n\n    local old_count=$(get_old_count \"$old_count_file\") || return 1\n    local repo_pw=$(get_mysql_password)\n    local current_count=$(get_blazegraph_count \"$namespace\")\n    local inserted_triples=$(get_inserted_triples_count \"$repo_pw\")\n\n    local uninserted_count=0\n    if uninserted=$(get_uninserted_count \"$namespace\"); then\n        uninserted_count=$uninserted\n        echo \"[WARN] There are ${uninserted_count#-} uninserted quads in the chunks folder.\"\n        echo \"Feel free to run the import.sh script again.\"\n        echo \"*Note: Some chunks may need to be manually imported\"\n    fi\n\n    local actual_count=$((current_count - inserted_triples + uninserted_count))\n    local expected_total=$((old_count))\n\n    percentage_diff=$(echo \"scale=6; 100 * ($actual_count - $expected_total) / $expected_total\" | bc)\n\n    abs_diff=$(echo \"$percentage_diff\" | sed 's/-//')\n\n    within_threshold=$(echo \"$abs_diff <= 0.05\" | bc)\n\n    if [ \"$within_threshold\" -eq 1 ]; then\n        if [ $uninserted_count -eq 0 ]; then\n            echo \"[SUCCESS] The migration has been completed successfully! There are no uninserted quads.\"\n        else\n            echo \"[SUCCESS] The migration has been completed successfully thus far.\"\n        fi\n        return 0\n    else\n        local difference=$((actual_count - expected_total))\n        if [ $difference -gt 0 ]; then\n            echo \"[ERROR] There are $difference more quads than expected\"\n            echo \"*Note: Errors can happen during importing, if this number is of a small value, it can be ignored\"\n        else\n            echo \"[ERROR] There are ${difference#-} fewer quads than expected\"\n            echo \"*Note: Errors can happen during importing, if this number is of a small value, it can be ignored\"\n        fi\n        return 1\n    fi\n}\n\ncheck_quad_count \"dkg\"\n"
  },
  {
    "path": "blazegraph-migration/export.sh",
    "content": "#!/bin/bash\n\nset -e\n\n# Configs\nBLAZEGRAPH_JAR=\"blazegraph.jar\"\n\nif [ \"$#\" -lt 2 ]; then\n  echo \"Usage: $0 <base_dir> <namespace1> [namespace2 ...]\"\n  exit 1\nfi\n\nBASE_DIR=\"$1\"\nshift\n\nlog() {\n  echo \"[$(date '+%Y-%m-%d %H:%M:%S')] $1\"\n}\n\n# Ensure Blazegraph and Otnode are started on script exit (success or error)\ncleanup() {\n  log \"Ensuring Blazegraph service is running...\"\n  sudo systemctl start blazegraph.service\n  log \"Ensuring Otnode service is running...\"\n  sudo systemctl restart otnode.service\n}\ntrap cleanup EXIT\n\nlog \"Stopping otnode service...\"\nsudo systemctl stop otnode.service\nsleep 10\n\nfor NAMESPACE in \"$@\"; do\n  FOLDER_NAME=$(echo \"$NAMESPACE\" | tr '-' '_')\n  EXPORT_DIR=\"$BASE_DIR/$FOLDER_NAME\"\n  EXPORT_FILE=\"$EXPORT_DIR/$FOLDER_NAME/data.nq.gz\"\n  QUAD_COUNT_FILE=\"OLD_QUAD_COUNT_${NAMESPACE}.txt\"\n  PROPERTIES_FILE=\"$EXPORT_DIR/${NAMESPACE}.properties\"\n  BLAZEGRAPH_URL=\"http://localhost:9999/blazegraph/namespace/$NAMESPACE/sparql\"\n\n  log \"Processing namespace: $NAMESPACE\"\n\n  log \"Querying quad count from Blazegraph...\"\n  QUAD_COUNT=$(curl -s -X POST \"$BLAZEGRAPH_URL\" \\\n    -H \"Accept: text/tab-separated-values\" \\\n    --data-urlencode 'query=SELECT (COUNT(*) AS ?total) WHERE { GRAPH ?g { ?s ?p ?o } }' \\\n    | tail -n 1)\n\n  if ! [[ \"$QUAD_COUNT\" =~ ^[0-9]+$ ]]; then\n    log \"❌ Invalid quad count received: '$QUAD_COUNT'\"\n    exit 1\n  fi\n\n  echo \"$QUAD_COUNT\" > \"$QUAD_COUNT_FILE\"\n  log \"Quad count: $QUAD_COUNT (saved to $QUAD_COUNT_FILE)\"\n\n  log \"Stopping blazegraph service...\"\n  sudo systemctl stop blazegraph.service\n  sleep 10\n\n  log \"Creating properties file for $NAMESPACE...\"\n  mkdir -p \"$EXPORT_DIR\"\n  cat > \"$PROPERTIES_FILE\" <<EOF\ncom.bigdata.namespace.${NAMESPACE}.spo.com.bigdata.btree.BTree.branchingFactor=1024\ncom.bigdata.rdf.store.AbstractTripleStore.textIndex=false\ncom.bigdata.rdf.store.AbstractTripleStore.justify=false\ncom.bigdata.rdf.store.AbstractTripleStore.statementIdentifiers=false\ncom.bigdata.rdf.store.AbstractTripleStore.axiomsClass=com.bigdata.rdf.axioms.NoAxioms\ncom.bigdata.rdf.sail.namespace=${NAMESPACE}\ncom.bigdata.rdf.store.AbstractTripleStore.quads=true\ncom.bigdata.namespace.${NAMESPACE}.lex.com.bigdata.btree.BTree.branchingFactor=400\ncom.bigdata.rdf.store.AbstractTripleStore.geoSpatial=false\ncom.bigdata.journal.Journal.groupCommit=false\ncom.bigdata.rdf.sail.isolatableIndices=false\ncom.bigdata.rdf.store.AbstractTripleStore.enableRawRecordsSupport=false\ncom.bigdata.rdf.store.AbstractTripleStore.Options.inlineTextLiterals=true\ncom.bigdata.rdf.store.AbstractTripleStore.Options.maxInlineTextLength=128\ncom.bigdata.rdf.store.AbstractTripleStore.Options.blobsThreshold=256\ncom.bigdata.journal.AbstractJournal.file=${BASE_DIR}/blazegraph.jnl\nEOF\n\n  log \"Exporting KB to N-Quads...\"\n  java -Xmx6g -cp \"$BLAZEGRAPH_JAR\" \\\n    com.bigdata.rdf.sail.ExportKB \\\n    -outdir \"$EXPORT_DIR\" \\\n    -format N-Quads \\\n    \"$PROPERTIES_FILE\" \"$NAMESPACE\" > \"$EXPORT_DIR/output.log\" 2>&1\n\n  log \"Export complete. Output should be in $EXPORT_FILE\"\n\n  if [ ! -f \"$EXPORT_FILE\" ]; then\n    log \"❌ Export file not found: $EXPORT_FILE\"\n    exit 1\n  fi\n\n  log \"Validating line count...\"\n  LINE_COUNT=$(zcat \"$EXPORT_FILE\" | wc -l)\n\n  if [ \"$LINE_COUNT\" -eq \"$QUAD_COUNT\" ]; then\n    log \"✅ Line count matches quad count: $LINE_COUNT lines\"\n  else\n    log \"❌ MISMATCH for $NAMESPACE: exported $LINE_COUNT lines, expected $QUAD_COUNT\"\n    exit 1\n  fi\n\n  log \"Starting blazegraph service...\"\n  sudo systemctl start blazegraph.service\n  until nc -z localhost 9999; do\n    sleep 1\n  done\ndone\n\nlog \"Stopping blazegraph service to remove old journal...\"\nsudo systemctl stop blazegraph.service\nsleep 10\nlog \"Removing old Blazegraph journal...\"\nrm -f \"${BASE_DIR}/blazegraph.jnl\"\n\nlog \"Creating new journal on blazegraph start...\"\nsudo systemctl start blazegraph.service\n\nlog \"Resetting triple log in MySQL...\"\nREPO_PW=$(grep ^REPOSITORY_PASSWORD= \"$BASE_DIR/current/.env\" | cut -d '=' -f2)\nmysql -u root -p\"$REPO_PW\" operationaldb -e \"UPDATE triples_insert_count SET count = 0 WHERE id = 1;\"\n\nlog \"✅ Migration completed for all namespaces.\""
  },
  {
    "path": "blazegraph-migration/import.sh",
    "content": "#!/bin/bash\n\nLOG_FILE=\"migration_import_$(date +%Y%m%d_%H%M%S).log\"\nCURL_TIMEOUT=300\nMAX_RETRIES=3\nCHUNK_DELAY=2\n\nlog_message() {\n    local level=\"$1\"\n    local message=\"$2\"\n    local timestamp=$(date \"+%Y-%m-%d %H:%M:%S\")\n    echo \"[$timestamp] [$level] $message\" | tee -a \"$LOG_FILE\"\n}\n\nconvert_namespace() {\n    local input=\"$1\"\n    echo \"${input//_/-}\"\n}\n\nfind_target_folders() {\n    local folders=()\n\n    if [ -d \"dkg\" ]; then\n        folders+=(\"dkg\")\n    fi\n\n    for folder in paranet*; do\n        if [ -d \"$folder\" ]; then\n            folders+=(\"$folder\")\n        fi\n    done\n    echo \"${folders[@]}\"\n}\n\nsplit_into_chunks() {\n    local input_dir=\"$1\"\n    local chunk_size=\"$2\"\n    local input_file=\"$input_dir/$input_dir/data.nq.gz\"\n    local output_dir=\"$input_dir/chunks\"\n    mkdir -p \"$output_dir\"\n\n    log_message \"INFO\" \"Starting file split for $input_file\"\n    gunzip -c \"$input_file\" | split -l \"$chunk_size\" --additional-suffix=.nq - \"${output_dir}/chunk_\"\n    log_message \"INFO\" \"File split completed. Output directory: $output_dir\"\n}\n\nprocess_chunk() {\n    local chunk_file=\"$1\"\n    local chunk_num=\"$2\"\n    local blazegraph_url=\"$3\"\n    local total_chunks=\"$4\"\n    local retry_count=0\n    local success=false\n\n    while [ $retry_count -lt $MAX_RETRIES ] && [ \"$success\" = false ]; do\n        if [ $retry_count -gt 0 ]; then\n            log_message \"INFO\" \"Retry $retry_count for chunk $chunk_num\"\n        fi\n\n        local chunk_lines=$(wc -l < \"$chunk_file\")\n        local response=$(curl -s --max-time $CURL_TIMEOUT -X POST \\\n             -H \"Content-Type: text/x-nquads\" \\\n             --data-binary @\"$chunk_file\" \\\n             \"$blazegraph_url\" 2>&1)\n\n        if [[ $response == *\"<?xml version=\\\"1.0\\\"?><data modified=\"* ]]; then\n            local modified=$(echo \"$response\" | grep -o 'modified=\"[0-9]*\"' | cut -d'\"' -f2)\n\n            if [ \"$chunk_lines\" -eq \"$modified\" ]; then\n                success=true\n                log_message \"INFO\" \"Successfully processed chunk $chunk_num/$total_chunks\"\n                rm \"$chunk_file\"\n            else\n                log_message \"ERROR\" \"Chunk $chunk_num processing failed: line count mismatch (expected: $chunk_lines, modified: $modified)\"\n                retry_count=$((retry_count + 1))\n            fi\n        else\n            log_message \"ERROR\" \"Chunk $chunk_num processing failed: $response\"\n            retry_count=$((retry_count + 1))\n        fi\n\n        if [ $retry_count -lt $MAX_RETRIES ] && [ \"$success\" = false ]; then\n            sleep 5\n        fi\n    done\n\n    if [ \"$success\" = false ]; then\n        log_message \"ERROR\" \"Failed to process chunk $chunk_num after $MAX_RETRIES attempts\"\n        sleep 5\n        return 1\n    fi\n\n    sleep 10\n    return 0\n}\n\nprocess_chunks() {\n    local input_dir=\"$1\"\n    local chunk_dir=\"$input_dir/chunks\"\n    local processed_chunks=0\n    local failed_chunks=0\n    local namespace=$(convert_namespace \"$(basename \"$input_dir\")\")\n    local blazegraph_url=\"http://localhost:9999/blazegraph/namespace/$namespace/sparql\"\n\n    if [ ! -d \"$chunk_dir\" ]; then\n        log_message \"ERROR\" \"Chunks directory not found: $chunk_dir\"\n        return 1\n    fi\n\n    local chunks=(\"$chunk_dir\"/*.nq)\n    total_chunks=${#chunks[@]}\n\n    if [ $total_chunks -eq 0 ]; then\n        log_message \"ERROR\" \"No .nq files found in chunks directory: $chunk_dir\"\n        return 1\n    fi\n\n    log_message \"INFO\" \"Starting chunk processing for namespace: $namespace\"\n    log_message \"INFO\" \"Total chunks to process: $total_chunks\"\n\n    local chunk_num=1\n    for chunk in \"${chunks[@]}\"; do\n        job_pool_run process_chunk \"$chunk\" \"$chunk_num\" \"$blazegraph_url\" \"$total_chunks\"\n        chunk_num=$((chunk_num + 1))\n    done\n\n    job_pool_wait\n\n    local remaining_chunks=(\"$chunk_dir\"/*.nq)\n    local remaining_chunks_count=${#remaining_chunks[@]}\n\n    log_message \"INFO\" \"Chunk processing completed for namespace: $namespace\"\n\n    if [ $remaining_chunks_count -eq 1 ]; then\n        log_message \"INFO\" \"All chunks processed successfully.\"\n        return 0\n    else\n        log_message \"ERROR\" \"Some chunks were not processed. Remaining chunks: $remaining_chunks_count\"\n        return 1\n    fi\n}\n\n. ./current/blazegraph-migration/job_pool.sh\n\njob_pool_init 5 0\n\nchunk_size=50000\ntarget_folders=($(find_target_folders))\n\nif [ ${#target_folders[@]} -eq 0 ]; then\n    log_message \"ERROR\" \"No target folders found (dkg or paranet*)\"\n    exit 1\nfi\n\nlog_message \"INFO\" \"Found ${#target_folders[@]} target folders: ${target_folders[*]}\"\n\nfor folder in \"${target_folders[@]}\"; do\n    log_message \"INFO\" \"Processing folder: $folder\"\n    if [ ! -f \"$folder/$folder/data.nq.gz\" ]; then\n        log_message \"ERROR\" \"Required file not found: $folder/$folder/data.nq.gz\"\n        continue\n    fi\n\n    if [ ! -d \"$folder/chunks\" ]; then\n        split_into_chunks \"$folder\" \"$chunk_size\"\n    else\n        log_message \"INFO\" \"Using existing chunks directory: $folder/chunks\"\n    fi\n\n    process_chunks \"$folder\"\ndone\n\nlog_message \"INFO\" \"Migration process completed\"\n\njob_pool_shutdown\n"
  },
  {
    "path": "blazegraph-migration/job_pool.sh",
    "content": "# Job pooling for bash shell scripts\n# This script provides a job pooling functionality where you can keep up to n\n# processes/functions running in parallel so that you don't saturate a system\n# with concurrent processes.\n#\n# Got inspiration from http://stackoverflow.com/questions/6441509/how-to-write-a-process-pool-bash-shell\n#\n# Copyright (c) 2012 Vince Tse\n# with changes by Geoff Clements (c) 2014\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n# end-of-jobs marker\njob_pool_end_of_jobs=\"JOBPOOL_END_OF_JOBS\"\n\n# job queue used to send jobs to the workers\njob_pool_job_queue=/tmp/job_pool_job_queue_$$\n\n# where to run results to\njob_pool_result_log=/tmp/job_pool_result_log_$$\n\n# toggle command echoing\njob_pool_echo_command=0\n\n# number of parallel jobs allowed.  also used to determine if job_pool_init\n# has been called when jobs are queued.\njob_pool_pool_size=-1\n\n# \\brief variable to check for number of non-zero exits\njob_pool_nerrors=0\n\n################################################################################\n# private functions\n################################################################################\n\n# \\brief debug output\nfunction _job_pool_echo()\n{\n    if [[ \"${job_pool_echo_command}\" == \"1\" ]]; then\n        echo $@\n    fi\n}\n\n# \\brief cleans up\nfunction _job_pool_cleanup()\n{\n    rm -f ${job_pool_job_queue} ${job_pool_result_log}\n}\n\n# \\brief signal handler\nfunction _job_pool_exit_handler()\n{\n    _job_pool_stop_workers\n    _job_pool_cleanup\n}\n\n# \\brief print the exit codes for each command\n# \\param[in] result_log  the file where the exit codes are written to\nfunction _job_pool_print_result_log()\n{\n    job_pool_nerrors=$(grep ^ERROR \"${job_pool_result_log}\" | wc -l)\n    cat \"${job_pool_result_log}\" | sed -e 's/^ERROR//'\n}\n                                                                                                                               # \\brief the worker function that is called when we fork off worker processes\n# \\param[in] id  the worker ID\n# \\param[in] job_queue  the fifo to read jobs from\n# \\param[in] result_log  the temporary log file to write exit codes to\nfunction _job_pool_worker()\n{\n    local id=$1\n    local job_queue=$2\n    local result_log=$3\n    local cmd=\n    local args=\n\n    exec 7<> ${job_queue}\n    while [[ \"${cmd}\" != \"${job_pool_end_of_jobs}\" && -e \"${job_queue}\" ]]; do\n        # workers block on the exclusive lock to read the job queue\n        flock --exclusive 7\n        IFS=$'\\v'\n        read cmd args <${job_queue}\n        set -- ${args}\n        unset IFS\n        flock --unlock 7\n        # the worker should exit if it sees the end-of-job marker or run the\n        # job otherwise and save its exit code to the result log.\n        if [[ \"${cmd}\" == \"${job_pool_end_of_jobs}\" ]]; then\n            # write it one more time for the next sibling so that everyone\n            # will know we are exiting.\n            echo \"${cmd}\" >&7\n        else\n            _job_pool_echo \"### _job_pool_worker-${id}: ${cmd}\"\n            # run the job\n            { ${cmd} \"$@\" ; }\n            # now check the exit code and prepend \"ERROR\" to the result log entry\n            # which we will use to count errors and then strip out later.\n            local result=$?\n            local status=\n            if [[ \"${result}\" != \"0\" ]]; then\n                status=ERROR\n            fi\n            # now write the error to the log, making sure multiple processes\n            # don't trample over each other.\n            exec 8<> ${result_log}\n            flock --exclusive 8\n            _job_pool_echo \"${status}job_pool: exited ${result}: ${cmd} $@\" >> ${result_log}\n            flock --unlock 8\n            exec 8>&-\n            _job_pool_echo \"### _job_pool_worker-${id}: exited ${result}: ${cmd} $@\"\n        fi\n    done\n    exec 7>&-\n}\n\n# \\brief sends message to worker processes to stop\nfunction _job_pool_stop_workers()\n{\n    # send message to workers to exit, and wait for them to stop before\n    # doing cleanup.\n    echo ${job_pool_end_of_jobs} >> ${job_pool_job_queue}\n    wait\n}\n\n# \\brief fork off the workers\n# \\param[in] job_queue  the fifo used to send jobs to the workers\n# \\param[in] result_log  the temporary log file to write exit codes to\nfunction _job_pool_start_workers()\n{\n    local job_queue=$1\n    local result_log=$2\n    for ((i=0; i<${job_pool_pool_size}; i++)); do\n        _job_pool_worker ${i} ${job_queue} ${result_log} &\n    done\n}\n\n################################################################################\n# public functions\n################################################################################\n\n# \\brief initializes the job pool\n# \\param[in] pool_size  number of parallel jobs allowed\n# \\param[in] echo_command  1 to turn on echo, 0 to turn off\nfunction job_pool_init()\n{\n    local pool_size=$1\n    local echo_command=$2\n\n    # set the global attibutes\n    job_pool_pool_size=${pool_size:=1}\n    job_pool_echo_command=${echo_command:=0}\n\n    # create the fifo job queue and create the exit code log\n    rm -rf ${job_pool_job_queue} ${job_pool_result_log}\n    mkfifo ${job_pool_job_queue}\n    touch ${job_pool_result_log}\n\n    # fork off the workers\n    _job_pool_start_workers ${job_pool_job_queue} ${job_pool_result_log}\n}\n\n# \\brief waits for all queued up jobs to complete and shuts down the job pool\nfunction job_pool_shutdown()\n{\n    _job_pool_stop_workers\n    _job_pool_print_result_log\n    _job_pool_cleanup\n}\n\n# \\brief run a job in the job pool\nfunction job_pool_run()\n{\n    if [[ \"${job_pool_pool_size}\" == \"-1\" ]]; then\n        job_pool_init\n    fi\n    printf \"%s\\v\" \"$@\" >> ${job_pool_job_queue}\n    echo >> ${job_pool_job_queue}\n}\n\n# \\brief waits for all queued up jobs to complete before starting new jobs\n# This function actually fakes a wait by telling the workers to exit\n# when done with the jobs and then restarting them.\nfunction job_pool_wait()\n{\n    _job_pool_stop_workers\n    _job_pool_start_workers ${job_pool_job_queue} ${job_pool_result_log}\n}\n#########################################\n# End of Job Pool\n#########################################\n"
  },
  {
    "path": "config/config.json",
    "content": "{\n    \"development\": {\n        \"modules\": {\n            \"autoUpdater\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"ot-auto-updater\": {\n                        \"enabled\": false,\n                        \"package\": \"./auto-updater/implementation/ot-auto-updater.js\",\n                        \"config\": {\n                            \"branch\": \"v8/develop\"\n                        }\n                    }\n                }\n            },\n            \"httpClient\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"express-http-client\": {\n                        \"enabled\": true,\n                        \"package\": \"./http-client/implementation/express-http-client.js\",\n                        \"config\": {\n                            \"useSsl\": false,\n                            \"port\": 8900,\n                            \"sslKeyPath\": \"/root/certs/privkey.pem\",\n                            \"sslCertificatePath\": \"/root/certs/fullchain.pem\",\n                            \"rateLimiter\": {\n                                \"timeWindowSeconds\": 60,\n                                \"maxRequests\": 10\n                            }\n                        }\n                    }\n                }\n            },\n            \"network\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"libp2p-service\": {\n                        \"enabled\": true,\n                        \"package\": \"./network/implementation/libp2p-service.js\",\n                        \"config\": {\n                            \"dht\": {\n                                \"kBucketSize\": 20\n                            },\n                            \"nat\": {\n                                \"enabled\": false,\n                                \"externalIp\": null\n                            },\n                            \"connectionManager\": {\n                                \"autoDial\": true,\n                                \"autoDialInterval\": 10e3,\n                                \"dialTimeout\": 2e3\n                            },\n                            \"peerRouting\": {\n                                \"refreshManager\": {\n                                    \"enabled\": true,\n                                    \"interval\": 6e5,\n                                    \"bootDelay\": 2e3\n                                }\n                            },\n                            \"port\": 9100,\n                            \"bootstrap\": []\n                        }\n                    }\n                }\n            },\n            \"repository\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"sequelize-repository\": {\n                        \"enabled\": true,\n                        \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                        \"config\": {\n                            \"database\": \"operationaldb\",\n                            \"user\": \"root\",\n                            \"password\": \"\",\n                            \"port\": \"3306\",\n                            \"host\": \"localhost\",\n                            \"dialect\": \"mysql\",\n                            \"logging\": false,\n                            \"pool\": {\n                                \"max\": 120,\n                                \"min\": 0,\n                                \"acquire\": 60000,\n                                \"idle\": 10000,\n                                \"evict\": 1000\n                            }\n                        }\n                    }\n                }\n            },\n            \"tripleStore\": {\n                \"enabled\": true,\n                \"timeout\": {\n                    \"query\": 60000,\n                    \"get\": 10000,\n                    \"batchGet\": 10000,\n                    \"insert\": 300000,\n                    \"ask\": 10000\n                },\n                \"implementation\": {\n                    \"ot-blazegraph\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                        \"config\": {}\n                    },\n                    \"ot-fuseki\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-fuseki/ot-fuseki.js\",\n                        \"config\": {}\n                    },\n                    \"ot-graphdb\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-graphdb/ot-graphdb.js\",\n                        \"config\": {}\n                    },\n                    \"ot-neptune\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-neptune/ot-neptune.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"validation\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"merkle-validation\": {\n                        \"enabled\": true,\n                        \"package\": \"./validation/implementation/merkle-validation.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"blockchain\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"hardhat1:31337\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain/implementation/hardhat/hardhat-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                            \"rpcEndpoints\": [\"http://localhost:8545\"],\n                            \"evmManagementPublicKey\": \"0x1B420da5f7Be66567526E32bc68ab29F1A63765A\",\n                            \"initialStakeAmount\": 50000,\n                            \"initialAskAmount\": 0.2,\n                            \"operatorFee\": 0\n                        }\n                    },\n                    \"hardhat2:31337\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain/implementation/hardhat/hardhat-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                            \"rpcEndpoints\": [\"http://localhost:9545\"],\n                            \"evmManagementPublicKey\": \"0x1B420da5f7Be66567526E32bc68ab29F1A63765A\",\n                            \"initialStakeAmount\": 50000,\n                            \"initialAskAmount\": 0.2,\n                            \"operatorFee\": 0\n                        }\n                    }\n                }\n            },\n            \"telemetry\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"quest-telemetry\": {\n                        \"enabled\": true,\n                        \"package\": \"./telemetry/implementation/quest-telemetry.js\",\n                        \"config\": {\n                            \"localEndpoint\": \"http::addr=localhost:10000\",\n                            \"signalingServiceEndpoint\": \"\",\n                            \"sendToSignalingService\": false\n                        }\n                    }\n                }\n            },\n            \"blockchainEvents\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-ethers\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain-events/implementation/ot-ethers/ot-ethers.js\",\n                        \"config\": {\n                            \"blockchains\": [\"hardhat1:31337\", \"hardhat2:31337\"],\n                            \"rpcEndpoints\": {\n                                \"hardhat1:31337\": [\"http://localhost:8545\"],\n                                \"hardhat2:31337\": [\"http://localhost:9545\"]\n                            },\n                            \"hubContractAddress\": {\n                                \"hardhat1:31337\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                                \"hardhat2:31337\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\"\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"maximumAssertionSizeInKb\": 500000,\n        \"commandExecutorVerboseLoggingEnabled\": false,\n        \"appDataPath\": \"data\",\n        \"logging\": {\n            \"defaultLevel\": \"trace\",\n            \"enableExperimentalScopes\": true\n        },\n        \"assetSync\": {\n            \"syncDKG\": {\n                \"enabled\": true,\n                \"syncBatchSize\": 5,\n                \"doneThreshold\": 95\n            },\n            \"syncParanets\": []\n        },\n        \"auth\": {\n            \"ipBasedAuthEnabled\": true,\n            \"tokenBasedAuthEnabled\": false,\n            \"loggingEnabled\": true,\n            \"ipWhitelist\": [\"::1\", \"127.0.0.1\"],\n            \"publicOperations\": [],\n            \"bothIpAndTokenAuthRequired\": false\n        }\n    },\n    \"test\": {\n        \"modules\": {\n            \"autoUpdater\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"ot-auto-updater\": {\n                        \"enabled\": false,\n                        \"package\": \"./auto-updater/implementation/ot-auto-updater.js\",\n                        \"config\": {\n                            \"branch\": \"v8/develop\"\n                        }\n                    }\n                }\n            },\n            \"httpClient\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"express-http-client\": {\n                        \"enabled\": true,\n                        \"package\": \"./http-client/implementation/express-http-client.js\",\n                        \"config\": {\n                            \"useSsl\": false,\n                            \"sslKeyPath\": \"/root/certs/privkey.pem\",\n                            \"sslCertificatePath\": \"/root/certs/fullchain.pem\",\n                            \"rateLimiter\": {\n                                \"timeWindowSeconds\": 60,\n                                \"maxRequests\": 10\n                            }\n                        }\n                    }\n                }\n            },\n            \"network\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"libp2p-service\": {\n                        \"enabled\": true,\n                        \"package\": \"./network/implementation/libp2p-service.js\",\n                        \"config\": {\n                            \"dht\": {\n                                \"kBucketSize\": 20\n                            },\n                            \"nat\": {\n                                \"enabled\": false,\n                                \"externalIp\": null\n                            },\n                            \"connectionManager\": {\n                                \"autoDial\": true,\n                                \"autoDialInterval\": 10e3,\n                                \"dialTimeout\": 2e3\n                            },\n                            \"peerRouting\": {\n                                \"refreshManager\": {\n                                    \"enabled\": true,\n                                    \"interval\": 6e5,\n                                    \"bootDelay\": 2e3\n                                }\n                            },\n                            \"port\": 9000,\n                            \"bootstrap\": [\n                                \"/ip4/0.0.0.0/tcp/9000/p2p/QmWyf3dtqJnhuCpzEDTNmNFYc5tjxTrXhGcUUmGHdg2gtj\"\n                            ]\n                        }\n                    }\n                }\n            },\n            \"validation\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"enabled\": true,\n                    \"merkle-validation\": {\n                        \"package\": \"./validation/implementation/merkle-validation.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"repository\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"sequelize-repository\": {\n                        \"enabled\": true,\n                        \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                        \"config\": {\n                            \"database\": \"operationaldb\",\n                            \"user\": \"root\",\n                            \"password\": \"\",\n                            \"port\": \"3306\",\n                            \"host\": \"localhost\",\n                            \"dialect\": \"mysql\",\n                            \"logging\": false,\n                            \"pool\": {\n                                \"max\": 120,\n                                \"min\": 0,\n                                \"acquire\": 60000,\n                                \"idle\": 10000,\n                                \"evict\": 1000\n                            }\n                        }\n                    }\n                }\n            },\n            \"blockchain\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"hardhat1:31337\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain/implementation/hardhat/hardhat-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                            \"rpcEndpoints\": [\"http://localhost:8545\"],\n                            \"initialStakeAmount\": 50000,\n                            \"initialAskAmount\": 0.2,\n                            \"operatorFee\": 0\n                        }\n                    },\n                    \"hardhat2:31337\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain/implementation/hardhat/hardhat-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                            \"rpcEndpoints\": [\"http://localhost:9545\"],\n                            \"initialStakeAmount\": 50000,\n                            \"initialAskAmount\": 0.2,\n                            \"operatorFee\": 0\n                        }\n                    }\n                }\n            },\n            \"tripleStore\": {\n                \"enabled\": true,\n                \"timeout\": {\n                    \"query\": 60000,\n                    \"get\": 10000,\n                    \"batchGet\": 10000,\n                    \"insert\": 300000,\n                    \"ask\": 10000\n                },\n                \"implementation\": {\n                    \"ot-blazegraph\": {\n                        \"enabled\": true,\n                        \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                        \"config\": {}\n                    },\n                    \"ot-neptune\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-neptune/ot-neptune.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"telemetry\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"quest-telemetry\": {\n                        \"enabled\": true,\n                        \"package\": \"./telemetry/implementation/quest-telemetry.js\",\n                        \"config\": {\n                            \"localEndpoint\": \"http::addr=localhost:10000\",\n                            \"signalingServiceEndpoint\": \"\",\n                            \"sendToSignalingService\": false\n                        }\n                    }\n                }\n            },\n            \"blockchainEvents\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-ethers\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain-events/implementation/ot-ethers/ot-ethers.js\",\n                        \"config\": {\n                            \"blockchains\": [\"hardhat1:31337\", \"hardhat2:31337\"],\n                            \"rpcEndpoints\": {\n                                \"hardhat1:31337\": [\"http://localhost:8545\"],\n                                \"hardhat2:31337\": [\"http://localhost:9545\"]\n                            },\n                            \"hubContractAddress\": {\n                                \"hardhat1:31337\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                                \"hardhat2:31337\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\"\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"maximumAssertionSizeInKb\": 500000,\n        \"commandExecutorVerboseLoggingEnabled\": false,\n        \"appDataPath\": \"data\",\n        \"logging\": {\n            \"defaultLevel\": \"info\",\n            \"enableExperimentalScopes\": true\n        },\n        \"assetSync\": {\n            \"syncDKG\": {\n                \"enabled\": false,\n                \"syncBatchSize\": 20,\n                \"doneThreshold\": 95\n            },\n            \"syncParanets\": []\n        },\n        \"auth\": {\n            \"ipBasedAuthEnabled\": true,\n            \"tokenBasedAuthEnabled\": false,\n            \"loggingEnabled\": true,\n            \"ipWhitelist\": [\"::1\", \"127.0.0.1\"],\n            \"publicOperations\": [],\n            \"bothIpAndTokenAuthRequired\": false\n        }\n    },\n    \"testnet\": {\n        \"modules\": {\n            \"autoUpdater\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-auto-updater\": {\n                        \"enabled\": true,\n                        \"package\": \"./auto-updater/implementation/ot-auto-updater.js\",\n                        \"config\": {\n                            \"branch\": \"v6/release/testnet\"\n                        }\n                    }\n                }\n            },\n            \"network\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"libp2p-service\": {\n                        \"enabled\": true,\n                        \"package\": \"./network/implementation/libp2p-service.js\",\n                        \"config\": {\n                            \"dht\": {\n                                \"kBucketSize\": 20\n                            },\n                            \"nat\": {\n                                \"enabled\": true,\n                                \"externalIp\": null\n                            },\n                            \"connectionManager\": {\n                                \"autoDial\": true,\n                                \"autoDialInterval\": 10e3,\n                                \"dialTimeout\": 2e3\n                            },\n                            \"peerRouting\": {\n                                \"refreshManager\": {\n                                    \"enabled\": true,\n                                    \"interval\": 6e5,\n                                    \"bootDelay\": 2e3\n                                }\n                            },\n                            \"port\": 9000,\n                            \"bootstrap\": [\n                                \"/ip4/164.92.138.30/tcp/9000/p2p/QmbiZQm18JefDizrQwbRhPgkaLykTLyrUEpeMWuKJHXuUM\",\n                                \"/ip4/139.59.145.152/tcp/9000/p2p/Qme2oF6afixBjLYjF5CYeC73d5dygsTq8P7BPQp31NVkye\"\n                            ]\n                        }\n                    }\n                }\n            },\n            \"httpClient\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"express-http-client\": {\n                        \"enabled\": true,\n                        \"package\": \"./http-client/implementation/express-http-client.js\",\n                        \"config\": {\n                            \"useSsl\": false,\n                            \"port\": 8900,\n                            \"sslKeyPath\": \"/root/certs/privkey.pem\",\n                            \"sslCertificatePath\": \"/root/certs/fullchain.pem\",\n                            \"rateLimiter\": {\n                                \"timeWindowSeconds\": 60,\n                                \"maxRequests\": 10\n                            }\n                        }\n                    }\n                }\n            },\n            \"repository\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"sequelize-repository\": {\n                        \"enabled\": true,\n                        \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                        \"config\": {\n                            \"database\": \"operationaldb\",\n                            \"user\": \"root\",\n                            \"password\": \"password\",\n                            \"port\": \"3306\",\n                            \"host\": \"localhost\",\n                            \"dialect\": \"mysql\",\n                            \"logging\": false,\n                            \"pool\": {\n                                \"max\": 120,\n                                \"min\": 0,\n                                \"acquire\": 60000,\n                                \"idle\": 10000,\n                                \"evict\": 1000\n                            }\n                        }\n                    }\n                }\n            },\n            \"blockchain\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"otp:20430\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/ot-parachain/ot-parachain-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0xe233b5b78853a62b1e11ebe88bf083e25b0a57a6\",\n                            \"rpcEndpoints\": [\n                                \"https://lofar-testnet.origin-trail.network\",\n                                \"https://lofar-testnet.origintrail.network\"\n                            ],\n                            \"operatorFee\": 0\n                        }\n                    },\n                    \"gnosis:10200\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/gnosis/gnosis-service.js\",\n\n                        \"config\": {\n                            \"hubContractAddress\": \"0x2c08AC4B630c009F709521e56Ac385A6af70650f\",\n                            \"gasPriceOracleLink\": \"https://blockscout.chiadochain.net/api/v1/gas-price-oracle\",\n                            \"rpcEndpoints\": [\"https://rpc.chiadochain.net\"],\n                            \"operatorFee\": 0\n                        }\n                    },\n                    \"base:84532\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/base/base-service.js\",\n\n                        \"config\": {\n                            \"hubContractAddress\": \"0xf21CE8f8b01548D97DCFb36869f1ccB0814a4e05\",\n                            \"rpcEndpoints\": [\"https://sepolia.base.org\"],\n                            \"operatorFee\": 0\n                        }\n                    }\n                }\n            },\n            \"validation\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"merkle-validation\": {\n                        \"enabled\": true,\n                        \"package\": \"./validation/implementation/merkle-validation.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"tripleStore\": {\n                \"enabled\": true,\n                \"timeout\": {\n                    \"query\": 60000,\n                    \"get\": 10000,\n                    \"batchGet\": 10000,\n                    \"insert\": 300000,\n                    \"ask\": 10000\n                },\n                \"implementation\": {\n                    \"ot-blazegraph\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                        \"config\": {}\n                    },\n                    \"ot-fuseki\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-fuseki/ot-fuseki.js\",\n                        \"config\": {}\n                    },\n                    \"ot-graphdb\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-graphdb/ot-graphdb.js\",\n                        \"config\": {}\n                    },\n                    \"ot-neptune\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-neptune/ot-neptune.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"telemetry\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"quest-telemetry\": {\n                        \"enabled\": true,\n                        \"package\": \"./telemetry/implementation/quest-telemetry.js\",\n                        \"config\": {\n                            \"localEndpoint\": \"http::addr=localhost:10000\",\n                            \"signalingServiceEndpoint\": \"\",\n                            \"sendToSignalingService\": false\n                        }\n                    }\n                }\n            },\n            \"blockchainEvents\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-ethers\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain-events/implementation/ot-ethers/ot-ethers.js\",\n                        \"config\": {\n                            \"blockchains\": [\"otp:20430\", \"gnosis:10200\", \"base:84532\"],\n                            \"rpcEndpoints\": {\n                                \"base:84532\": [\"https://sepolia.base.org\"],\n                                \"otp:20430\": [\n                                    \"https://lofar-testnet.origin-trail.network\",\n                                    \"https://lofar-testnet.origintrail.network\"\n                                ],\n                                \"gnosis:10200\": [\"https://rpc.chiadochain.net\"]\n                            },\n                            \"hubContractAddress\": {\n                                \"base:84532\": \"0xf21CE8f8b01548D97DCFb36869f1ccB0814a4e05\",\n                                \"otp:20430\": \"0xe233b5b78853a62b1e11ebe88bf083e25b0a57a6\",\n                                \"gnosis:10200\": \"0x2c08AC4B630c009F709521e56Ac385A6af70650f\"\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"maximumAssertionSizeInKb\": 500000,\n        \"commandExecutorVerboseLoggingEnabled\": false,\n        \"appDataPath\": \"data\",\n        \"logging\": {\n            \"defaultLevel\": \"trace\",\n            \"enableExperimentalScopes\": true\n        },\n        \"assetSync\": {\n            \"syncDKG\": {\n                \"enabled\": false,\n                \"syncBatchSize\": 20,\n                \"doneThreshold\": 95\n            },\n            \"syncParanets\": []\n        },\n        \"auth\": {\n            \"ipBasedAuthEnabled\": true,\n            \"tokenBasedAuthEnabled\": false,\n            \"loggingEnabled\": true,\n            \"ipWhitelist\": [\"::1\", \"127.0.0.1\"],\n            \"publicOperations\": [],\n            \"bothIpAndTokenAuthRequired\": false\n        }\n    },\n    \"devnet\": {\n        \"modules\": {\n            \"autoUpdater\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-auto-updater\": {\n                        \"enabled\": true,\n                        \"package\": \"./auto-updater/implementation/ot-auto-updater.js\",\n                        \"config\": {\n                            \"branch\": \"v8/develop\"\n                        }\n                    }\n                }\n            },\n            \"network\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"libp2p-service\": {\n                        \"enabled\": true,\n                        \"package\": \"./network/implementation/libp2p-service.js\",\n                        \"config\": {\n                            \"dht\": {\n                                \"kBucketSize\": 20\n                            },\n                            \"nat\": {\n                                \"enabled\": true,\n                                \"externalIp\": null\n                            },\n                            \"connectionManager\": {\n                                \"autoDial\": true,\n                                \"autoDialInterval\": 10e3,\n                                \"dialTimeout\": 2e3\n                            },\n                            \"peerRouting\": {\n                                \"refreshManager\": {\n                                    \"enabled\": true,\n                                    \"interval\": 6e5,\n                                    \"bootDelay\": 2e3\n                                }\n                            },\n                            \"port\": 9000,\n                            \"bootstrap\": [\n                                \"/ip4/64.225.99.151/tcp/9000/p2p/QmawsTRqaLPyLQ5PfStpFcpQW4bvNQ59zV1by2G5aJHuVn\"\n                            ]\n                        }\n                    }\n                }\n            },\n            \"httpClient\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"express-http-client\": {\n                        \"enabled\": true,\n                        \"package\": \"./http-client/implementation/express-http-client.js\",\n                        \"config\": {\n                            \"useSsl\": false,\n                            \"port\": 8900,\n                            \"sslKeyPath\": \"/root/certs/privkey.pem\",\n                            \"sslCertificatePath\": \"/root/certs/fullchain.pem\",\n                            \"rateLimiter\": {\n                                \"timeWindowSeconds\": 60,\n                                \"maxRequests\": 10\n                            }\n                        }\n                    }\n                }\n            },\n            \"repository\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"sequelize-repository\": {\n                        \"enabled\": true,\n                        \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                        \"config\": {\n                            \"database\": \"operationaldb\",\n                            \"user\": \"root\",\n                            \"password\": \"password\",\n                            \"port\": \"3306\",\n                            \"host\": \"localhost\",\n                            \"dialect\": \"mysql\",\n                            \"logging\": false,\n                            \"pool\": {\n                                \"max\": 120,\n                                \"min\": 0,\n                                \"acquire\": 60000,\n                                \"idle\": 10000,\n                                \"evict\": 1000\n                            }\n                        }\n                    }\n                }\n            },\n            \"blockchain\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"base:84532\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/base/base-service.js\",\n\n                        \"config\": {\n                            \"hubContractAddress\": \"0xE043daF4cC8ae2c720ef95fc82574a37a429c40A\",\n                            \"rpcEndpoints\": [\"https://sepolia.base.org\"],\n                            \"operatorFee\": 0\n                        }\n                    }\n                }\n            },\n            \"validation\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"merkle-validation\": {\n                        \"enabled\": true,\n                        \"package\": \"./validation/implementation/merkle-validation.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"tripleStore\": {\n                \"enabled\": true,\n                \"timeout\": {\n                    \"query\": 60000,\n                    \"get\": 10000,\n                    \"batchGet\": 10000,\n                    \"insert\": 300000,\n                    \"ask\": 10000\n                },\n                \"implementation\": {\n                    \"ot-blazegraph\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                        \"config\": {}\n                    },\n                    \"ot-fuseki\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-fuseki/ot-fuseki.js\",\n                        \"config\": {}\n                    },\n                    \"ot-graphdb\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-graphdb/ot-graphdb.js\",\n                        \"config\": {}\n                    },\n                    \"ot-neptune\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-neptune/ot-neptune.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"telemetry\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"quest-telemetry\": {\n                        \"enabled\": true,\n                        \"package\": \"./telemetry/implementation/quest-telemetry.js\",\n                        \"config\": {\n                            \"localEndpoint\": \"http::addr=localhost:10000\",\n                            \"signalingServiceEndpoint\": \"\",\n                            \"sendToSignalingService\": false\n                        }\n                    }\n                }\n            },\n            \"blockchainEvents\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-ethers\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain-events/implementation/ot-ethers/ot-ethers.js\",\n                        \"config\": {\n                            \"blockchains\": [\"base:84532\"],\n                            \"rpcEndpoints\": {\n                                \"base:84532\": [\"https://sepolia.base.org\"]\n                            },\n                            \"hubContractAddress\": {\n                                \"base:84532\": \"0xE043daF4cC8ae2c720ef95fc82574a37a429c40A\"\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"maximumAssertionSizeInKb\": 500000,\n        \"commandExecutorVerboseLoggingEnabled\": false,\n        \"appDataPath\": \"data\",\n        \"logging\": {\n            \"defaultLevel\": \"trace\",\n            \"enableExperimentalScopes\": false\n        },\n        \"assetSync\": {\n            \"syncDKG\": {\n                \"enabled\": false,\n                \"syncBatchSize\": 20,\n                \"doneThreshold\": 95\n            },\n            \"syncParanets\": []\n        },\n        \"auth\": {\n            \"ipBasedAuthEnabled\": true,\n            \"tokenBasedAuthEnabled\": false,\n            \"loggingEnabled\": true,\n            \"ipWhitelist\": [\"::1\", \"127.0.0.1\"],\n            \"publicOperations\": [],\n            \"bothIpAndTokenAuthRequired\": false\n        }\n    },\n    \"mainnet\": {\n        \"modules\": {\n            \"autoUpdater\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-auto-updater\": {\n                        \"enabled\": true,\n                        \"package\": \"./auto-updater/implementation/ot-auto-updater.js\",\n                        \"config\": {\n                            \"branch\": \"v6/release/mainnet\"\n                        }\n                    }\n                }\n            },\n            \"network\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"libp2p-service\": {\n                        \"enabled\": true,\n                        \"package\": \"./network/implementation/libp2p-service.js\",\n                        \"config\": {\n                            \"dht\": {\n                                \"kBucketSize\": 20\n                            },\n                            \"nat\": {\n                                \"enabled\": true,\n                                \"externalIp\": null\n                            },\n                            \"connectionManager\": {\n                                \"autoDial\": true,\n                                \"autoDialInterval\": 10e3,\n                                \"dialTimeout\": 2e3\n                            },\n                            \"peerRouting\": {\n                                \"refreshManager\": {\n                                    \"enabled\": true,\n                                    \"interval\": 6e5,\n                                    \"bootDelay\": 2e3\n                                }\n                            },\n                            \"port\": 9000,\n                            \"bootstrap\": [\n                                \"/ip4/157.230.96.194/tcp/9000/p2p/QmZFcns6eGUosD96beHyevKu1jGJ1bA56Reg2f1J4q59Jt\",\n                                \"/ip4/18.132.135.102/tcp/9000/p2p/QmemqyXyvrTAm7PwrcTcFiEEFx69efdR92GSZ1oQprbdja\"\n                            ]\n                        }\n                    }\n                }\n            },\n            \"httpClient\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"express-http-client\": {\n                        \"enabled\": true,\n                        \"package\": \"./http-client/implementation/express-http-client.js\",\n                        \"config\": {\n                            \"useSsl\": false,\n                            \"port\": 8900,\n                            \"sslKeyPath\": \"/root/certs/privkey.pem\",\n                            \"sslCertificatePath\": \"/root/certs/fullchain.pem\",\n                            \"rateLimiter\": {\n                                \"timeWindowSeconds\": 60,\n                                \"maxRequests\": 10\n                            }\n                        }\n                    }\n                }\n            },\n            \"repository\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"sequelize-repository\": {\n                        \"enabled\": true,\n                        \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                        \"config\": {\n                            \"database\": \"operationaldb\",\n                            \"user\": \"root\",\n                            \"password\": \"password\",\n                            \"port\": \"3306\",\n                            \"host\": \"localhost\",\n                            \"dialect\": \"mysql\",\n                            \"logging\": false,\n                            \"pool\": {\n                                \"max\": 120,\n                                \"min\": 0,\n                                \"acquire\": 60000,\n                                \"idle\": 10000,\n                                \"evict\": 1000\n                            }\n                        }\n                    }\n                }\n            },\n            \"blockchain\": {\n                \"enabled\": true,\n                \"defaultImplementation\": \"otp:2043\",\n                \"implementation\": {\n                    \"otp:2043\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/ot-parachain/ot-parachain-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0x0957e25BD33034948abc28204ddA54b6E1142D6F\",\n                            \"rpcEndpoints\": [\n                                \"https://astrosat-parachain-rpc.origin-trail.network\",\n                                \"https://astrosat.origintrail.network/\",\n                                \"https://astrosat-2.origintrail.network/\"\n                            ],\n                            \"operatorFee\": 0\n                        }\n                    },\n                    \"gnosis:100\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/gnosis/gnosis-service.js\",\n                        \"config\": {\n                            \"hubContractAddress\": \"0x882D0BF07F956b1b94BBfe9E77F47c6fc7D4EC8f\",\n                            \"gasPriceOracleLink\": \"https://gnosis.blockscout.com/api/v1/gas-price-oracle\",\n                            \"operatorFee\": 0\n                        }\n                    },\n                    \"base:8453\": {\n                        \"enabled\": false,\n                        \"package\": \"./blockchain/implementation/base/base-service.js\",\n\n                        \"config\": {\n                            \"hubContractAddress\": \"0x99Aa571fD5e681c2D27ee08A7b7989DB02541d13\",\n                            \"operatorFee\": 0\n                        }\n                    }\n                }\n            },\n            \"tripleStore\": {\n                \"enabled\": true,\n                \"timeout\": {\n                    \"query\": 60000,\n                    \"get\": 10000,\n                    \"batchGet\": 10000,\n                    \"insert\": 300000,\n                    \"ask\": 10000\n                },\n                \"implementation\": {\n                    \"ot-blazegraph\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                        \"config\": {}\n                    },\n                    \"ot-fuseki\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-fuseki/ot-fuseki.js\",\n                        \"config\": {}\n                    },\n                    \"ot-graphdb\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-graphdb/ot-graphdb.js\",\n                        \"config\": {}\n                    },\n                    \"ot-neptune\": {\n                        \"enabled\": false,\n                        \"package\": \"./triple-store/implementation/ot-neptune/ot-neptune.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"validation\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"merkle-validation\": {\n                        \"enabled\": true,\n                        \"package\": \"./validation/implementation/merkle-validation.js\",\n                        \"config\": {}\n                    }\n                }\n            },\n            \"telemetry\": {\n                \"enabled\": false,\n                \"implementation\": {\n                    \"quest-telemetry\": {\n                        \"enabled\": true,\n                        \"package\": \"./telemetry/implementation/quest-telemetry.js\",\n                        \"config\": {\n                            \"localEndpoint\": \"http::addr=localhost:10000\",\n                            \"signalingServiceEndpoint\": \"\",\n                            \"sendToSignalingService\": false\n                        }\n                    }\n                }\n            },\n            \"blockchainEvents\": {\n                \"enabled\": true,\n                \"implementation\": {\n                    \"ot-ethers\": {\n                        \"enabled\": true,\n                        \"package\": \"./blockchain-events/implementation/ot-ethers/ot-ethers.js\",\n                        \"config\": {\n                            \"blockchains\": [\"otp:2043\", \"gnosis:100\", \"base:8453\"],\n                            \"rpcEndpoints\": {\n                                \"otp:2043\": [\n                                    \"https://astrosat-parachain-rpc.origin-trail.network\",\n                                    \"https://astrosat.origintrail.network/\",\n                                    \"https://astrosat-2.origintrail.network/\"\n                                ]\n                            },\n                            \"hubContractAddress\": {\n                                \"otp:2043\": \"0x0957e25BD33034948abc28204ddA54b6E1142D6F\",\n                                \"gnosis:100\": \"0x882D0BF07F956b1b94BBfe9E77F47c6fc7D4EC8f\",\n                                \"base:8453\": \"0x99Aa571fD5e681c2D27ee08A7b7989DB02541d13\"\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"maximumAssertionSizeInKb\": 500000,\n        \"commandExecutorVerboseLoggingEnabled\": false,\n        \"appDataPath\": \"data\",\n        \"logging\": {\n            \"defaultLevel\": \"trace\",\n            \"enableExperimentalScopes\": false\n        },\n        \"assetSync\": {\n            \"syncDKG\": {\n                \"enabled\": false,\n                \"syncBatchSize\": 20,\n                \"doneThreshold\": 95\n            },\n            \"syncParanets\": []\n        },\n        \"auth\": {\n            \"ipBasedAuthEnabled\": true,\n            \"tokenBasedAuthEnabled\": false,\n            \"loggingEnabled\": true,\n            \"ipWhitelist\": [\"::1\", \"127.0.0.1\"],\n            \"publicOperations\": [],\n            \"bothIpAndTokenAuthRequired\": false\n        }\n    }\n}\n"
  },
  {
    "path": "config/papertrail.yml",
    "content": "files:\n  - /ot-node/complete-node.log\ndestination:\n  host: logs4.papertrailapp.com\n  port: 39178\n  protocol: tls\n"
  },
  {
    "path": "cucumber.js",
    "content": "export default {\n    retry: 1,\n    failFast: false,\n    backtrace: true,\n};\n"
  },
  {
    "path": "dependencies.md",
    "content": "# OT-node dependencies\n\n## dev dependencies\n\n##### [@cucumber/cucumber](https://www.npmjs.com/package/@cucumber/cucumber)\n\n-   **version**: ^8.5.2\n-   **description**: used to execute bdd tests\n\n##### [chai](https://www.npmjs.com/package/chai)\n\n-   **version**: ^4.3.6\n-   **description**: assertion library for bdd tests\n\n##### [dkg.js](https://www.npmjs.com/package/dkg.js)\n\n-   **version**: ^6.0.2\n-   **description**: dkg client used in bdd tests\n\n##### [eslint](https://www.npmjs.com/package/eslint)\n\n-   **version**: ^8.23.0\n-   **description**: code linter\n\n##### [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb)\n\n-   **version**: ^19.0.4\n-   **description**: linter plugin\n\n##### [eslint-config-prettier](https://www.npmjs.com/package/eslint-config-prettier)\n\n-   **version**: ^8.5.0\n-   **description**: linter plugin\n\n##### [husky](https://www.npmjs.com/package/husky)\n\n-   **version**: ^8.0.1\n-   **description**: used to run lint-staged as pre commit\n\n##### [lint-staged](https://www.npmjs.com/package/lint-staged)\n\n-   **version**: ^13.0.3\n-   **description**: code linter for pre commits\n\n##### [mocha](https://www.npmjs.com/package/mocha)\n\n-   **version**: ^10.0.0\n-   **description**: test framework used in unit tests\n\n##### [nyc](https://www.npmjs.com/package/nyc)\n\n-   **version**: ^15.1.0\n-   **description**: command line interface used for running mocha\n\n##### [prettier](https://www.npmjs.com/package/prettier)\n\n-   **version**: ^2.7.1\n-   **description**: code formatter\n\n##### [sinon](https://www.npmjs.com/package/sinon)\n\n-   **version**: ^14.0.0\n-   **description**: used to create sandboxes in unit tests\n\n##### [slugify](https://www.npmjs.com/package/slugify)\n\n-   **version**: ^1.6.5\n-   **description**: used to stringify cucumber test scenarios\n\n## dependencies\n\n##### [@comunica/query-sparql](https://www.npmjs.com/package/@comunica/query-sparql)\n\n-   **version**: ^2.4.3\n-   **description**: sparql query engine\n\n##### [@ethersproject/bytes](https://www.npmjs.com/package/@ethersproject/bytes)\n\n-   **version**: ^5.7.0\n-   **description**: Used for creating substrate and evm accounts mapping signatures in `create-account-mapping-signature.js`\n\n##### [@ethersproject/hash](https://www.npmjs.com/package/@ethersproject/hash)\n\n-   **version**: ^5.7.0\n-   **description**: Used for creating substrate and evm accounts mapping signatures in `create-account-mapping-signature.js`\n\n##### [@ethersproject/wallet](https://www.npmjs.com/package/@ethersproject/wallet)\n\n-   **version**: ^5.7.0\n-   **description**: Used for creating substrate and evm accounts mapping signatures in `create-account-mapping-signature.js`\n\n##### [@polkadot/api](https://www.npmjs.com/package/@polkadot/api)\n\n-   **version**: ^9.3.2\n-   **description**: used to interact with substrate nodes\n\n##### [@polkadot/keyring](https://www.npmjs.com/package/@polkadot/keyring)\n\n-   **version**: ^10.1.7\n-   **description**: Used for creating substrate and evm accounts mapping signatures in `create-account-mapping-signature.js`\n\n##### [@polkadot/util](https://www.npmjs.com/package/@polkadot/util)\n\n-   **version**: ^10.1.7\n-   **description**: Used for creating substrate and evm accounts mapping signatures in `create-account-mapping-signature.js`\n\n##### [@polkadot/util-crypto](https://www.npmjs.com/package/@polkadot/util-crypto)\n\n-   **version**: ^10.1.7\n-   **description**: Used for creating substrate and evm accounts mapping signatures in `create-account-mapping-signature.js`\n\n##### [app-root-path](https://www.npmjs.com/package/app-root-path)\n\n-   **version**: ^3.1.0\n-   **description**: used to determine root path\n\n##### [assertion-tools](https://www.npmjs.com/package/assertion-tools)\n\n-   **version**: ^2.0.2\n-   **description**: various functions used by both dkg.js and ot-node\n\n##### [async](https://www.npmjs.com/package/async)\n\n-   **version**: ^3.2.4\n-   **description**: used in `command-executor.js` to create an async queue to manage commands\n\n##### [async-mutex](https://www.npmjs.com/package/async-mutex)\n\n-   **version**: ^0.3.2\n-   **description**: used to avoid race conditions when updating sql repository\n\n##### [awilix](https://www.npmjs.com/package/awilix)\n\n-   **version**: ^7.0.3\n-   **description**: dependency injection container\n\n##### [axios](https://www.npmjs.com/package/axios)\n\n-   **version**: ^0.27.2\n-   **description**: http client used to make http requests\n\n##### [cors](https://www.npmjs.com/package/cors)\n\n-   **version**: ^2.8.5\n-   **description**: cors express middleware\n\n##### [deep-extend](https://www.npmjs.com/package/deep-extend)\n\n-   **version**: ^0.6.0\n-   **description**: used to merge users config and default config\n\n##### [dkg-evm-module](https://www.npmjs.com/package/dkg-evm-module)\n\n-   **version**: ^4.0.5\n-   **description**: used to import latest ot-node smart contracts abis\n\n##### [dotenv](https://www.npmjs.com/package/dotenv)\n\n-   **version**: ^16.0.1\n-   **description**: used for NODE_ENV variable\n\n##### [ethers](https://www.npmjs.com/package/ethers)\n\n-   **version**: ^5.7.2\n-   **description**: used to interact with blockchain nodes\n\n##### [express](https://www.npmjs.com/package/express)\n\n-   **version**: ^4.18.1\n-   **description**: used to handle http requests\n\n##### [express-fileupload](https://www.npmjs.com/package/express-fileupload)\n\n-   **version**: ^1.4.0\n-   **description**: express middleware **review required**\n\n##### [express-rate-limit](https://www.npmjs.com/package/express-rate-limit)\n\n-   **version**: ^6.5.2\n-   **description**: used to rate limit rpc requests\n\n##### [fs-extra](https://www.npmjs.com/package/fs-extra)\n\n-   **version**: ^10.1.0\n-   **description**: used for file system methods\n\n##### [graphdb](https://www.npmjs.com/package/graphdb)\n\n-   **version**: ^2.0.2\n-   **description**: used to create graphdb repositories if they don't exist\n\n##### [ip](https://www.npmjs.com/package/ip)\n\n-   **version**: ^1.1.8\n-   **description**: used to compare ip addresses\n\n##### [it-length-prefixed](https://www.npmjs.com/package/it-length-prefixed)\n\n-   **version**: ^5.0.3\n-   **description**: used to encode and decode streamed buffers in libp2p\n\n##### [it-map](https://www.npmjs.com/package/it-map)\n\n-   **version**: ^1.0.6\n-   **description**: used to map values received yielded by libp2p async iterators\n\n##### [it-pipe](https://www.npmjs.com/package/it-pipe)\n\n-   **version**: ^1.1.0\n-   **description**: stream pipeline that supports libp2p duplex streams. Used for streaming messages between nodes\n\n##### [jsonld](https://www.npmjs.com/package/jsonld)\n\n-   **version**: ^8.1.0\n-   **description**: used to canonize n-quads retrieved from db. **Should be moved to assertion-tools dependency**\n\n##### [jsonschema](https://www.npmjs.com/package/jsonschema)\n\n-   **version**: ^1.4.1\n-   **description**: used to validate ot-node rpc requests' bodies\n\n##### [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken)\n\n-   **version**: ^9.0.0\n-   **description**: used to generate, validate and decode JWTs\n\n##### [libp2p](https://www.npmjs.com/package/libp2p)\n\n-   **version**: ^0.32.4\n-   **description**: used for p2p network communication\n\n##### [libp2p-bootstrap](https://www.npmjs.com/package/libp2p-bootstrap)\n\n-   **version**: ^0.13.0\n-   **description**: used to define libp2p bootstrap nodes\n\n##### [libp2p-kad-dht](https://www.npmjs.com/package/libp2p-kad-dht)\n\n-   **version**: ^0.24.2\n-   **description**: used for libp2p kad dht initialisation\n\n##### [libp2p-mplex](https://www.npmjs.com/package/libp2p-mplex)\n\n-   **version**: ^0.10.7\n-   **description**: used for libp2p mplex initialisation\n\n##### [libp2p-noise](https://www.npmjs.com/package/libp2p-noise)\n\n-   **version**: ^4.0.0\n-   **description**: used for libp2p message encription\n\n##### [libp2p-tcp](https://www.npmjs.com/package/libp2p-tcp)\n\n-   **version**: ^0.17.2\n-   **description**: used for libp2p tcp communication\n\n##### [minimist](https://www.npmjs.com/package/minimist)\n\n-   **version**: ^1.2.7\n-   **description**: used to parse process arguments\n\n##### [ms](https://www.npmjs.com/package/ms)\n\n-   **version**: ^2.1.3\n-   **description**: convert expiration time to milliseconds in `token-generation.js`\n\n##### [mysql2](https://www.npmjs.com/package/mysql2)\n\n-   **version**: ^3.3.0\n-   **description**:\n\n##### [peer-id](https://www.npmjs.com/package/peer-id)\n\n-   **version**: ^0.15.3\n-   **description**: used to create network id\n\n##### [pino](https://www.npmjs.com/package/pino)\n\n-   **version**: ^8.4.2\n-   **description**: ot-node logger implementation\n\n##### [pino-pretty](https://www.npmjs.com/package/pino-pretty)\n\n-   **version**: ^9.1.0\n-   **description**: prettifier for pino logger\n\n##### [rc](https://www.npmjs.com/package/rc)\n\n-   **version**: ^1.2.8\n-   **description**: configuration loader\n\n##### [rolling-rate-limiter](https://www.npmjs.com/package/rolling-rate-limiter)\n\n-   **version**: ^0.2.13\n-   **description**: used to limit network requests\n\n##### [semver](https://www.npmjs.com/package/semver)\n\n-   **version**: ^7.3.7\n-   **description**: used to compare ot-node versions during auto update\n\n##### [sequelize](https://www.npmjs.com/package/sequelize)\n\n-   **version**: ^6.29.0\n-   **description**: used to communicate with sql repository\n\n##### [timeout-abort-controller](https://www.npmjs.com/package/timeout-abort-controller)\n\n-   **version**: ^3.0.0\n-   **description**: timeout network messages\n\n##### [toobusy-js](https://www.npmjs.com/package/toobusy-js)\n\n-   **version**: ^0.5.1\n-   **description**: used to check nodejs event loop lag\n\n##### [uint8arrays](https://www.npmjs.com/package/uint8arrays)\n\n-   **version**: ^3.1.0\n-   **description**: used to convert from string to buffer and from buffer to string\n\n##### [umzug](https://www.npmjs.com/package/umzug)\n\n-   **version**: ^3.2.1\n-   **description**: sequelize migration tool\n\n##### [unzipper](https://www.npmjs.com/package/unzipper)\n\n-   **version**: ^0.10.11\n-   **description**: unzip file during autoupdate\n\n##### [uuid](https://www.npmjs.com/package/uuid)\n\n-   **version**: ^8.3.2\n-   **description**: uuid generation\n"
  },
  {
    "path": "docker/docker-compose-alpine-blazegraph.yaml",
    "content": "version: '3'\n\nservices:\n  blazegraph:\n    container_name: blazegraph\n    image: origintrail/ot-node:blazegraph\n    network_mode: host\n\n\n  mysql:\n    container_name: mysql\n    image: mysql:8.0.17\n    environment:\n      MYSQL_ALLOW_EMPTY_PASSWORD: 1\n      MYSQL_ROOT_PASSWORD: null\n      MYSQL_DATABASE: operationaldb\n    expose:\n      - 3306\n    network_mode: host\n\n  ot-node:\n    container_name: ot-node\n    image: origintrail/ot-node:v6.0.0-beta.1-alpine\n    depends_on:\n      - blazegraph\n      - mysql\n    expose:\n      - 8900\n      - 9000\n    command:\n      - '/bin/sh'\n      - '-c'\n      - '/bin/sleep 25 && forever index.js'\n                   \n        \n    volumes:\n      - ${PWD}/.origintrail_noderc:/ot-node/.origintrail_noderc\n      - ~/certs/:/root/certs/\n    network_mode: host"
  },
  {
    "path": "docker/docker-compose-alpine-graphdb.yaml",
    "content": "version: '3'\n\nservices:\n  graphdb:\n    container_name: graphdb\n    image: khaller/graphdb-free:latest\n    network_mode: host\n\n\n  mysql:\n    container_name: mysql\n    image: mysql:8.0.17\n    environment:\n      MYSQL_ALLOW_EMPTY_PASSWORD: 1\n      MYSQL_ROOT_PASSWORD: null\n      MYSQL_DATABASE: operationaldb\n    expose:\n      - 3306\n    network_mode: host\n\n  ot-node:\n    container_name: ot-node\n    image: origintrail/ot-node:v6.0.0-beta.1-alpine\n    depends_on:\n      - graphdb\n      - mysql\n    expose:\n      - 8900\n      - 9000\n    command:\n      - '/bin/sh'\n      - '-c'\n      - '/bin/sleep 25 && forever index.js'\n                   \n        \n    volumes:\n      - ${PWD}/.origintrail_noderc:/ot-node/.origintrail_noderc\n      - ~/certs/:/root/certs/\n    network_mode: host"
  },
  {
    "path": "docker/docker-compose-debian-blazegraph.yaml",
    "content": "version: '3.8'\nservices:\n  blazegraph:\n    container_name: blazegraph\n    image: origintrail/ot-node:blazegraph\n    network_mode: host\n\n  ot-node:\n    container_name: ot-node\n    image: origintrail/ot-node:v6.0.0-beta.1-debian\n    depends_on:\n      - blazegraph\n    expose:\n      - 8900\n      - 9000\n    command: >\n       bash -c \"\n        /bin/sleep 30\n        service mariadb start &&\n        forever index.js\n        \"\n    volumes:\n      - ${PWD}/.origintrail_noderc:/ot-node/.origintrail_noderc\n      - ~/certs/:/root/certs/\n    network_mode: host"
  },
  {
    "path": "docker/docker-compose-debian-graphdb.yaml",
    "content": "version: '3.8'\nservices:\n  graphdb:\n    container_name: graphdb\n    image: khaller/graphdb-free:latest\n    network_mode: host\n\n  ot-node:\n    container_name: ot-node\n    image: origintrail/ot-node:v6.0.0-beta.1-debian\n    depends_on:\n      - graphdb\n    expose:\n      - 8900\n      - 9000\n    command: >\n       bash -c \"\n        /bin/sleep 30\n        service mariadb start &&\n        forever index.js\n        \"\n    volumes:\n      - ${PWD}/.origintrail_noderc:/ot-node/.origintrail_noderc\n      - ~/certs/:/root/certs/\n    network_mode: host\n"
  },
  {
    "path": "docker/docker-compose-ubuntu-blazegraph.yaml",
    "content": "version: '3.8'\nservices:\n  blazegraph:\n    container_name: blazegraph\n    image: origintrail/ot-node:blazegraph\n    network_mode: host\n\n  ot-node:\n    container_name: ot-node\n    image: origintrail/ot-node:v6.0.0-beta.1-ubuntu\n    depends_on:\n      - blazegraph\n    expose:\n      - 8900\n      - 9000\n    command: >\n       bash -c \"\n        /bin/sleep 35\n        service mysql restart && \n        forever index.js                \n        \"\n    volumes:\n      - ${PWD}/.origintrail_noderc:/ot-node/.origintrail_noderc\n      - ~/certs/:/root/certs/\n    network_mode: host\n    \n"
  },
  {
    "path": "docker/docker-compose-ubuntu-graphdb.yaml",
    "content": "version: '3.8'\nservices:\n  graphdb:\n    container_name: graphdb\n    image: khaller/graphdb-free:latest\n    network_mode: host\n\n  ot-node:\n    container_name: ot-node\n    image: origintrail/ot-node:v6.0.0-beta.1-ubuntu\n    depends_on:\n      - graphdb\n    expose:\n      - 8900\n      - 9000\n    command: >\n       bash -c \"\n        /bin/sleep 35\n        service mysql restart && \n        forever index.js                \n        \"\n    volumes:\n      - ${PWD}/.origintrail_noderc:/ot-node/.origintrail_noderc\n      - ~/certs/:/root/certs/\n    network_mode: host\n    \n"
  },
  {
    "path": "docs/openapi/DKGv8.yaml",
    "content": "openapi: 3.0.3\ninfo:\n  title: DKGv8\n  description: DKG V8 API Collection.\n  version: 1.0.0\n  contact: {}\nservers:\n  - url: localhost\npaths:\n  /info:\n    get:\n      tags:\n        - old\n      summary: Node Info\n      description: Get the node information.\n      operationId: nodeInfo\n      responses:\n        '200':\n          description: Node Info\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '20'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 12:43:07 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"14-Rq/28W5aGKCGXmXfM1+eW1LAbb4\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  version:\n                    type: string\n                    example: 6.0.13\n              examples:\n                Node Info:\n                  value:\n                    version: 6.0.13\n  /bid-suggestion:\n    get:\n      tags:\n        - old\n      summary: Get Bid Suggestion\n      description: Get bid suggestion based on provided parameters.\n      operationId: getBidSuggestion\n      parameters:\n        - name: blockchain\n          in: query\n          schema:\n            type: string\n            example: hardhat\n        - name: epochsNumber\n          in: query\n          schema:\n            type: string\n            example: '5'\n        - name: assertionSize\n          in: query\n          schema:\n            type: string\n            example: '299'\n        - name: contentAssetStorageAddress\n          in: query\n          schema:\n            type: string\n            example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n        - name: firstAssertionId\n          in: query\n          schema:\n            type: string\n            example: '0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf'\n        - name: hashFunctionId\n          in: query\n          schema:\n            type: string\n            example: '1'\n      requestBody:\n        content:\n          text/plain:\n            example: ''\n      responses:\n        '200':\n          description: Get Bid Suggestion\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '38'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 12:42:31 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"26-UrjseieOcIBnowM9obJae/FG7xc\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  bidSuggestion:\n                    type: string\n                    example: '903051579928002449'\n              examples:\n                Get Bid Suggestion:\n                  value:\n                    bidSuggestion: '903051579928002449'\n  /local-store:\n    post:\n      tags:\n        - old\n      summary: Local Store\n      description: Store locally.\n      operationId: localStore\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: array\n              items:\n                type: object\n                properties:\n                  assertion:\n                    type: array\n                    items:\n                      type: string\n                      example: <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                    example:\n                      - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                      - <uuid:1> <http://schema.org/company> 'OT' .\n                      - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                      - >-\n                        _:c14n0\n                        <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                        '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                        .\n                  assertionId:\n                    type: string\n                    example: >-\n                      0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                  blockchain:\n                    type: string\n                    example: hardhat\n                  contract:\n                    type: string\n                    example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                  storeType:\n                    type: string\n                    example: TRIPLE\n                  tokenId:\n                    type: number\n                    example: 0\n              example:\n                - assertion:\n                    - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                    - <uuid:1> <http://schema.org/company> 'OT' .\n                    - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                    - >-\n                      _:c14n0\n                      <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                      '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                      .\n                  assertionId: >-\n                    0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                  blockchain: hardhat\n                  contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                  storeType: TRIPLE\n                  tokenId: 0\n                - assertion:\n                    - <uuid:belgrade> <http://schema.org/postCode> '11000' .\n                    - <uuid:belgrade> <http://schema.org/title> 'Belgrade' .\n                    - <uuid:user:1> <http://schema.org/lastname> 'Smith' .\n                    - <uuid:user:1> <http://schema.org/name> 'Adam' .\n                  assertionId: >-\n                    0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9\n                  blockchain: hardhat\n                  contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                  storeType: TRIPLE\n                  tokenId: 0\n            example:\n              - assertion:\n                  - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                  - <uuid:1> <http://schema.org/company> 'OT' .\n                  - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                  - >-\n                    _:c14n0\n                    <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                    '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                    .\n                assertionId: >-\n                  0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                blockchain: hardhat\n                contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                storeType: TRIPLE\n                tokenId: 0\n              - assertion:\n                  - <uuid:belgrade> <http://schema.org/postCode> '11000' .\n                  - <uuid:belgrade> <http://schema.org/title> 'Belgrade' .\n                  - <uuid:user:1> <http://schema.org/lastname> 'Smith' .\n                  - <uuid:user:1> <http://schema.org/name> 'Adam' .\n                assertionId: >-\n                  0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9\n                blockchain: hardhat\n                contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                storeType: TRIPLE\n                tokenId: 0\n      responses:\n        '202':\n          description: Local Store\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 12:49:45 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-uF3l7SNXwSBVObRCAJxOmp8OJGc\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 7d499975-ce42-4d84-9092-0ac2a62f5151\n              examples:\n                Local Store:\n                  value:\n                    operationId: 7d499975-ce42-4d84-9092-0ac2a62f5151\n  /publish:\n    post:\n      tags:\n        - old\n      summary: Publish Knowledge Asset\n      description: Publish assertion.\n      operationId: publishKnowledgeAsset\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                assertion:\n                  type: array\n                  items:\n                    type: string\n                    example: <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                  example:\n                    - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                    - <uuid:1> <http://schema.org/company> 'OT' .\n                    - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                    - >-\n                      _:c14n0\n                      <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                      '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                      .\n                assertionId:\n                  type: string\n                  example: >-\n                    0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                blockchain:\n                  type: string\n                  example: hardhat\n                contract:\n                  type: string\n                  example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                hashFunctionId:\n                  type: number\n                  example: 1\n                tokenId:\n                  type: number\n                  example: 0\n            example:\n              assertion:\n                - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                - <uuid:1> <http://schema.org/company> 'OT' .\n                - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                - >-\n                  _:c14n0\n                  <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                  '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                  .\n              assertionId: >-\n                0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n              blockchain: hardhat\n              contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n              hashFunctionId: 1\n              tokenId: 0\n      responses:\n        '202':\n          description: Publish Knowledge Asset\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:07:57 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-SQS1f7vf+HLSUHZ6wvE9UUwksSY\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            RateLimit-Limit:\n              schema:\n                type: string\n                example: '10'\n            RateLimit-Remaining:\n              schema:\n                type: string\n                example: '9'\n            RateLimit-Reset:\n              schema:\n                type: string\n                example: '22'\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 8270c131-91b8-4573-a69e-504ff388a8b6\n              examples:\n                Publish Knowledge Asset:\n                  value:\n                    operationId: 8270c131-91b8-4573-a69e-504ff388a8b6\n  /get:\n    post:\n      tags:\n        - old\n      summary: Get Knowledge Asset\n      description: Get an assertion.\n      operationId: getKnowledgeAsset\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                hashFunctionId:\n                  type: number\n                  example: 1\n                id:\n                  type: string\n                  example: did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\n                state:\n                  type: string\n                  example: LATEST\n            example:\n              hashFunctionId: 1\n              id: did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\n              state: LATEST\n      responses:\n        '202':\n          description: Get Knowledge Asset\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:16:39 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-tXDgcL88Mx02VotKK9H3zPuWwf8\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            RateLimit-Limit:\n              schema:\n                type: string\n                example: '10'\n            RateLimit-Remaining:\n              schema:\n                type: string\n                example: '9'\n            RateLimit-Reset:\n              schema:\n                type: string\n                example: '12'\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 3a6df062-b3ce-4cac-aefa-77b1e8b9a4db\n              examples:\n                Get Knowledge Asset:\n                  value:\n                    operationId: 3a6df062-b3ce-4cac-aefa-77b1e8b9a4db\n  /update:\n    post:\n      tags:\n        - old\n      summary: Update Knowledge Asset\n      description: Update assertion.\n      operationId: updateKnowledgeAsset\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                assertion:\n                  type: array\n                  items:\n                    type: string\n                    example: <uuid:1> <http://schema.org/city> <uuid:Nis> .\n                  example:\n                    - <uuid:1> <http://schema.org/city> <uuid:Nis> .\n                    - <uuid:1> <http://schema.org/company> 'TL' .\n                    - <uuid:1> <http://schema.org/user> <uuid:user:2> .\n                    - >-\n                      _:c14n0\n                      <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                      '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63'\n                      .\n                assertionId:\n                  type: string\n                  example: >-\n                    0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\n                blockchain:\n                  type: string\n                  example: hardhat\n                contract:\n                  type: string\n                  example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                hashFunctionId:\n                  type: number\n                  example: 1\n                tokenId:\n                  type: number\n                  example: 0\n            example:\n              assertion:\n                - <uuid:1> <http://schema.org/city> <uuid:Nis> .\n                - <uuid:1> <http://schema.org/company> 'TL' .\n                - <uuid:1> <http://schema.org/user> <uuid:user:2> .\n                - >-\n                  _:c14n0\n                  <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                  '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63'\n                  .\n              assertionId: >-\n                0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\n              blockchain: hardhat\n              contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n              hashFunctionId: 1\n              tokenId: 0\n      responses:\n        '202':\n          description: Update Knowledge Asset\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:17:54 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-CjvPRlFINYIIcvR2H5gFBcOkNH8\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            RateLimit-Limit:\n              schema:\n                type: string\n                example: '10'\n            RateLimit-Remaining:\n              schema:\n                type: string\n                example: '9'\n            RateLimit-Reset:\n              schema:\n                type: string\n                example: '57'\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 0d4c3efc-0f0b-435d-b9a3-402748dbbb2f\n              examples:\n                Update Knowledge Asset:\n                  value:\n                    operationId: 0d4c3efc-0f0b-435d-b9a3-402748dbbb2f\n  /query:\n    post:\n      tags:\n        - old\n      summary: Query DKG\n      description: Execute a query.\n      operationId: queryDkg\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                query:\n                  type: string\n                  example: >-\n                    CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH\n                    <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9>\n                    { ?s ?p ?o . }}}\n                repository:\n                  type: string\n                  example: privateCurrent\n                type:\n                  type: string\n                  example: CONSTRUCT\n            example:\n              query: >-\n                CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH\n                <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9>\n                { ?s ?p ?o . }}}\n              repository: privateCurrent\n              type: CONSTRUCT\n      responses:\n        '202':\n          description: Query DKG\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:20:16 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-WRBDN6AcKKCbVi3DGfI6FvESm5w\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 746992ba-e607-4858-8deb-5cffc2541859\n              examples:\n                Query DKG:\n                  value:\n                    operationId: 746992ba-e607-4858-8deb-5cffc2541859\n  /{operation}/{operationId}:\n    get:\n      tags:\n        - v0\n      summary: '[v0] Get Operation Result'\n      description: Get result of a specific operation by its ID.\n      operationId: v0GetOperationResult\n      responses:\n        '200':\n          description: ''\n    parameters:\n      - name: operation\n        in: path\n        required: true\n        schema:\n          type: string\n      - name: operationId\n        in: path\n        required: true\n        schema:\n          type: string\n  /v0/info:\n    get:\n      tags:\n        - v0\n      summary: '[v0] Node Info'\n      description: Get the node information.\n      operationId: v0NodeInfo\n      responses:\n        '200':\n          description: '[v0] Node Info'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '20'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:27:58 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"14-Rq/28W5aGKCGXmXfM1+eW1LAbb4\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  version:\n                    type: string\n                    example: 6.0.13\n              examples:\n                '[v0] Node Info':\n                  value:\n                    version: 6.0.13\n  /v0/bid-suggestion:\n    get:\n      tags:\n        - v0\n      summary: '[v0] Get Bid Suggestion'\n      description: Get bid suggestion based on provided parameters.\n      operationId: v0GetBidSuggestion\n      parameters:\n        - name: blockchain\n          in: query\n          schema:\n            type: string\n            example: hardhat\n        - name: epochsNumber\n          in: query\n          schema:\n            type: string\n            example: '5'\n        - name: assertionSize\n          in: query\n          schema:\n            type: string\n            example: '299'\n        - name: contentAssetStorageAddress\n          in: query\n          schema:\n            type: string\n            example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n        - name: firstAssertionId\n          in: query\n          schema:\n            type: string\n            example: '0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf'\n        - name: hashFunctionId\n          in: query\n          schema:\n            type: string\n            example: '1'\n      requestBody:\n        content:\n          text/plain:\n            example: ''\n      responses:\n        '200':\n          description: '[v0] Get Bid Suggestion'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '39'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:59:02 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"27-ieFm/6t4DZwm0kFCMq71s37uy/g\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  bidSuggestion:\n                    type: string\n                    example: '1122511549276000025'\n              examples:\n                '[v0] Get Bid Suggestion':\n                  value:\n                    bidSuggestion: '1122511549276000025'\n  /v0/local-store:\n    post:\n      tags:\n        - v0\n      summary: '[v0] Local Store'\n      description: Store locally.\n      operationId: v0LocalStore\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: array\n              items:\n                type: object\n                properties:\n                  assertion:\n                    type: array\n                    items:\n                      type: string\n                      example: <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                    example:\n                      - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                      - <uuid:1> <http://schema.org/company> 'OT' .\n                      - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                      - >-\n                        _:c14n0\n                        <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                        '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                        .\n                  assertionId:\n                    type: string\n                    example: >-\n                      0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                  blockchain:\n                    type: string\n                    example: hardhat\n                  contract:\n                    type: string\n                    example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                  storeType:\n                    type: string\n                    example: TRIPLE\n                  tokenId:\n                    type: number\n                    example: 0\n              example:\n                - assertion:\n                    - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                    - <uuid:1> <http://schema.org/company> 'OT' .\n                    - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                    - >-\n                      _:c14n0\n                      <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                      '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                      .\n                  assertionId: >-\n                    0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                  blockchain: hardhat\n                  contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                  storeType: TRIPLE\n                  tokenId: 0\n                - assertion:\n                    - <uuid:belgrade> <http://schema.org/postCode> '11000' .\n                    - <uuid:belgrade> <http://schema.org/title> 'Belgrade' .\n                    - <uuid:user:1> <http://schema.org/lastname> 'Smith' .\n                    - <uuid:user:1> <http://schema.org/name> 'Adam' .\n                  assertionId: >-\n                    0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9\n                  blockchain: hardhat\n                  contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                  storeType: TRIPLE\n                  tokenId: 0\n            example:\n              - assertion:\n                  - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                  - <uuid:1> <http://schema.org/company> 'OT' .\n                  - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                  - >-\n                    _:c14n0\n                    <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                    '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                    .\n                assertionId: >-\n                  0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                blockchain: hardhat\n                contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                storeType: TRIPLE\n                tokenId: 0\n              - assertion:\n                  - <uuid:belgrade> <http://schema.org/postCode> '11000' .\n                  - <uuid:belgrade> <http://schema.org/title> 'Belgrade' .\n                  - <uuid:user:1> <http://schema.org/lastname> 'Smith' .\n                  - <uuid:user:1> <http://schema.org/name> 'Adam' .\n                assertionId: >-\n                  0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9\n                blockchain: hardhat\n                contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                storeType: TRIPLE\n                tokenId: 0\n      responses:\n        '202':\n          description: '[v0] Local Store'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:59:11 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-fpQtTlhbbWO7tqbMGm3CkKmOqaI\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 0a4ee669-95bb-41cd-a2e8-3382361e80d9\n              examples:\n                '[v0] Local Store':\n                  value:\n                    operationId: 0a4ee669-95bb-41cd-a2e8-3382361e80d9\n  /v0/publish:\n    post:\n      tags:\n        - v0\n      summary: '[v0] Publish Knowledge Asset'\n      description: Publish assertion.\n      operationId: v0PublishKnowledgeAsset\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                assertion:\n                  type: array\n                  items:\n                    type: string\n                    example: <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                  example:\n                    - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                    - <uuid:1> <http://schema.org/company> 'OT' .\n                    - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                    - >-\n                      _:c14n0\n                      <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                      '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                      .\n                assertionId:\n                  type: string\n                  example: >-\n                    0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n                blockchain:\n                  type: string\n                  example: hardhat\n                contract:\n                  type: string\n                  example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                hashFunctionId:\n                  type: number\n                  example: 1\n                tokenId:\n                  type: number\n                  example: 0\n            example:\n              assertion:\n                - <uuid:1> <http://schema.org/city> <uuid:belgrade> .\n                - <uuid:1> <http://schema.org/company> 'OT' .\n                - <uuid:1> <http://schema.org/user> <uuid:user:1> .\n                - >-\n                  _:c14n0\n                  <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                  '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9'\n                  .\n              assertionId: >-\n                0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\n              blockchain: hardhat\n              contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n              hashFunctionId: 1\n              tokenId: 0\n      responses:\n        '202':\n          description: '[v0] Publish Knowledge Asset'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 13:59:54 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-wKIhHpa0/tdVYh1Y8D2yINolruA\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            RateLimit-Limit:\n              schema:\n                type: string\n                example: '10'\n            RateLimit-Remaining:\n              schema:\n                type: string\n                example: '9'\n            RateLimit-Reset:\n              schema:\n                type: string\n                example: '52'\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 476fb996-db1a-47b8-8da4-80d71411feb3\n              examples:\n                '[v0] Publish Knowledge Asset':\n                  value:\n                    operationId: 476fb996-db1a-47b8-8da4-80d71411feb3\n  /v0/get:\n    post:\n      tags:\n        - v0\n      summary: '[v0] Get Knowledge Asset'\n      description: Get an assertion.\n      operationId: v0GetKnowledgeAsset\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                hashFunctionId:\n                  type: number\n                  example: 1\n                id:\n                  type: string\n                  example: did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\n                state:\n                  type: string\n                  example: LATEST\n            example:\n              hashFunctionId: 1\n              id: did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\n              state: LATEST\n      responses:\n        '202':\n          description: '[v0] Get Knowledge Asset'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 14:00:02 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-/27PH/ZH74wwVBrYDxWSzCk4yA0\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            RateLimit-Limit:\n              schema:\n                type: string\n                example: '10'\n            RateLimit-Remaining:\n              schema:\n                type: string\n                example: '9'\n            RateLimit-Reset:\n              schema:\n                type: string\n                example: '44'\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 5b34c048-2d08-4696-b3c4-c37c831b89ce\n              examples:\n                '[v0] Get Knowledge Asset':\n                  value:\n                    operationId: 5b34c048-2d08-4696-b3c4-c37c831b89ce\n  /v0/update:\n    post:\n      tags:\n        - v0\n      summary: '[v0] Update Knowledge Asset'\n      description: Update assertion.\n      operationId: v0UpdateKnowledgeAsset\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                assertion:\n                  type: array\n                  items:\n                    type: string\n                    example: <uuid:1> <http://schema.org/city> <uuid:Nis> .\n                  example:\n                    - <uuid:1> <http://schema.org/city> <uuid:Nis> .\n                    - <uuid:1> <http://schema.org/company> 'TL' .\n                    - <uuid:1> <http://schema.org/user> <uuid:user:2> .\n                    - >-\n                      _:c14n0\n                      <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                      '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63'\n                      .\n                assertionId:\n                  type: string\n                  example: >-\n                    0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\n                blockchain:\n                  type: string\n                  example: hardhat\n                contract:\n                  type: string\n                  example: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n                hashFunctionId:\n                  type: number\n                  example: 1\n                tokenId:\n                  type: number\n                  example: 0\n            example:\n              assertion:\n                - <uuid:1> <http://schema.org/city> <uuid:Nis> .\n                - <uuid:1> <http://schema.org/company> 'TL' .\n                - <uuid:1> <http://schema.org/user> <uuid:user:2> .\n                - >-\n                  _:c14n0\n                  <https://ontology.origintrail.io/dkg/1.0#privateAssertionID>\n                  '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63'\n                  .\n              assertionId: >-\n                0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\n              blockchain: hardhat\n              contract: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07'\n              hashFunctionId: 1\n              tokenId: 0\n      responses:\n        '202':\n          description: '[v0] Update Knowledge Asset'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 14:00:09 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-77qAdgCc/SEN47aETAww86PM04w\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            RateLimit-Limit:\n              schema:\n                type: string\n                example: '10'\n            RateLimit-Remaining:\n              schema:\n                type: string\n                example: '9'\n            RateLimit-Reset:\n              schema:\n                type: string\n                example: '38'\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: f0d34032-6910-49b4-a2a8-71c9f58feb58\n              examples:\n                '[v0] Update Knowledge Asset':\n                  value:\n                    operationId: f0d34032-6910-49b4-a2a8-71c9f58feb58\n  /v0/query:\n    post:\n      tags:\n        - v0\n      summary: '[v0] Query DKG'\n      description: Execute a query.\n      operationId: v0QueryDkg\n      requestBody:\n        content:\n          application/json:\n            schema:\n              type: object\n              properties:\n                query:\n                  type: string\n                  example: >-\n                    CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH\n                    <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9>\n                    { ?s ?p ?o . }}}\n                repository:\n                  type: string\n                  example: privateCurrent\n                type:\n                  type: string\n                  example: CONSTRUCT\n            example:\n              query: >-\n                CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH\n                <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9>\n                { ?s ?p ?o . }}}\n              repository: privateCurrent\n              type: CONSTRUCT\n      responses:\n        '202':\n          description: '[v0] Query DKG'\n          headers:\n            Access-Control-Allow-Origin:\n              schema:\n                type: string\n                example: '*'\n            Connection:\n              schema:\n                type: string\n                example: keep-alive\n            Content-Length:\n              schema:\n                type: string\n                example: '54'\n            Date:\n              schema:\n                type: string\n                example: Thu, 17 Aug 2023 14:00:19 GMT\n            ETag:\n              schema:\n                type: string\n                example: W/\"36-kqaRe64EoJygoEadXJWRekiCs4s\"\n            Keep-Alive:\n              schema:\n                type: string\n                example: timeout=5\n            X-Powered-By:\n              schema:\n                type: string\n                example: Express\n          content:\n            application/json:\n              schema:\n                type: object\n                properties:\n                  operationId:\n                    type: string\n                    example: 4d371ffb-a620-452f-8d16-3e427bafeae2\n              examples:\n                '[v0] Query DKG':\n                  value:\n                    operationId: 4d371ffb-a620-452f-8d16-3e427bafeae2\ntags:\n  - name: old\n  - name: v0\n"
  },
  {
    "path": "docs/postman/DKGv8.postman_collection.json",
    "content": "{\n\t\"info\": {\n\t\t\"_postman_id\": \"550b0443-cd47-482a-9c56-2b1229422426\",\n\t\t\"name\": \"DKGv8\",\n\t\t\"description\": \"DKG V8 API Collection.\",\n\t\t\"schema\": \"https://schema.getpostman.com/json/collection/v2.1.0/collection.json\"\n\t},\n\t\"item\": [\n\t\t{\n\t\t\t\"name\": \"old\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Node Info\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/info\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"info\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get the node information.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Node Info\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/info\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"info\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"OK\",\n\t\t\t\t\t\t\t\"code\": 200,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"20\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"14-Rq/28W5aGKCGXmXfM1+eW1LAbb4\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 12:43:07 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"version\\\": \\\"6.0.13\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Get Bid Suggestion\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disableBodyPruning\": true\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/bid-suggestion?blockchain={{blockchain}}&epochsNumber={{epochsNumber}}&assertionSize={{assertionSize}}&contentAssetStorageAddress={{contentAssetStorageAddress}}&firstAssertionId={{firstAssertionId}}&hashFunctionId={{hashFunctionId}}\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"bid-suggestion\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"blockchain\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{blockchain}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"epochsNumber\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{epochsNumber}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"assertionSize\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{assertionSize}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"contentAssetStorageAddress\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{contentAssetStorageAddress}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"firstAssertionId\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{firstAssertionId}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"hashFunctionId\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{hashFunctionId}}\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get bid suggestion based on provided parameters.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Get Bid Suggestion\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/bid-suggestion?blockchain={{blockchain}}&epochsNumber={{epochsNumber}}&assertionSize={{assertionSize}}&contentAssetStorageAddress={{contentAssetStorageAddress}}&firstAssertionId={{firstAssertionId}}&hashFunctionId={{hashFunctionId}}\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"bid-suggestion\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"blockchain\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{blockchain}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"epochsNumber\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{epochsNumber}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"assertionSize\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{assertionSize}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"contentAssetStorageAddress\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{contentAssetStorageAddress}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"firstAssertionId\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{firstAssertionId}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"hashFunctionId\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{hashFunctionId}}\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"OK\",\n\t\t\t\t\t\t\t\"code\": 200,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"38\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"26-UrjseieOcIBnowM9obJae/FG7xc\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 12:42:31 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"bidSuggestion\\\": \\\"903051579928002449\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Local Store\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{{assertions}}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/local-store\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"local-store\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Store locally.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Local Store\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{assertions}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/local-store\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"local-store\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-uF3l7SNXwSBVObRCAJxOmp8OJGc\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 12:49:45 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"7d499975-ce42-4d84-9092-0ac2a62f5151\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Publish Knowledge Asset\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disabledSystemHeaders\": {\n\t\t\t\t\t\t\t\"content-type\": true\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:belgrade> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'OT' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:1> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/publish\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"publish\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Publish assertion.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Publish Knowledge Asset\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:belgrade> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'OT' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:1> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/publish\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"publish\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Limit\",\n\t\t\t\t\t\t\t\t\t\"value\": \"10\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Remaining\",\n\t\t\t\t\t\t\t\t\t\"value\": \"9\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Reset\",\n\t\t\t\t\t\t\t\t\t\"value\": \"22\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-SQS1f7vf+HLSUHZ6wvE9UUwksSY\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:07:57 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"8270c131-91b8-4573-a69e-504ff388a8b6\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Get Knowledge Asset\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disabledSystemHeaders\": {}\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"id\\\": \\\"did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\\\",\\n  \\\"state\\\": \\\"LATEST\\\",\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/get\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"get\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get an assertion.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Get Knowledge Asset\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"id\\\": \\\"did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\\\",\\n  \\\"state\\\": \\\"LATEST\\\",\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/get\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"get\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Limit\",\n\t\t\t\t\t\t\t\t\t\"value\": \"10\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Remaining\",\n\t\t\t\t\t\t\t\t\t\"value\": \"9\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Reset\",\n\t\t\t\t\t\t\t\t\t\"value\": \"12\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-tXDgcL88Mx02VotKK9H3zPuWwf8\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:16:39 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"3a6df062-b3ce-4cac-aefa-77b1e8b9a4db\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Update Knowledge Asset\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:Nis> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'TL' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:2> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/update\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"update\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Update assertion.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Update Knowledge Asset\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:Nis> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'TL' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:2> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/update\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"update\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Limit\",\n\t\t\t\t\t\t\t\t\t\"value\": \"10\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Remaining\",\n\t\t\t\t\t\t\t\t\t\"value\": \"9\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Reset\",\n\t\t\t\t\t\t\t\t\t\"value\": \"57\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-CjvPRlFINYIIcvR2H5gFBcOkNH8\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:17:54 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"0d4c3efc-0f0b-435d-b9a3-402748dbbb2f\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Query DKG\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"query\\\": \\\"CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9> { ?s ?p ?o . }}}\\\",\\n  \\\"type\\\": \\\"CONSTRUCT\\\",\\n  \\\"repository\\\": \\\"privateCurrent\\\"\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/query\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"query\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Execute a query.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"Query DKG\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"query\\\": \\\"CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9> { ?s ?p ?o . }}}\\\",\\n  \\\"type\\\": \\\"CONSTRUCT\\\",\\n  \\\"repository\\\": \\\"privateCurrent\\\"\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/query\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"query\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-WRBDN6AcKKCbVi3DGfI6FvESm5w\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:20:16 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"746992ba-e607-4858-8deb-5cffc2541859\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"Get Operation Result\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/{{operation}}/{{operationId}}\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"{{operation}}\",\n\t\t\t\t\t\t\t\t\"{{operationId}}\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get result of a specific operation by its ID.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"name\": \"v0\",\n\t\t\t\"item\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Node Info\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/info\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"info\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get the node information.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Node Info\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/info\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"info\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"OK\",\n\t\t\t\t\t\t\t\"code\": 200,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"20\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"14-Rq/28W5aGKCGXmXfM1+eW1LAbb4\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:27:58 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"version\\\": \\\"6.0.13\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Get Bid Suggestion\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disableBodyPruning\": true\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/bid-suggestion?blockchain={{blockchain}}&epochsNumber={{epochsNumber}}&assertionSize={{assertionSize}}&contentAssetStorageAddress={{contentAssetStorageAddress}}&firstAssertionId={{firstAssertionId}}&hashFunctionId={{hashFunctionId}}\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"bid-suggestion\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"blockchain\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{blockchain}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"epochsNumber\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{epochsNumber}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"assertionSize\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{assertionSize}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"contentAssetStorageAddress\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{contentAssetStorageAddress}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"firstAssertionId\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{firstAssertionId}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"hashFunctionId\",\n\t\t\t\t\t\t\t\t\t\"value\": \"{{hashFunctionId}}\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get bid suggestion based on provided parameters.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Get Bid Suggestion\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/bid-suggestion?blockchain={{blockchain}}&epochsNumber={{epochsNumber}}&assertionSize={{assertionSize}}&contentAssetStorageAddress={{contentAssetStorageAddress}}&firstAssertionId={{firstAssertionId}}&hashFunctionId={{hashFunctionId}}\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"bid-suggestion\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"query\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"blockchain\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{blockchain}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"epochsNumber\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{epochsNumber}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"assertionSize\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{assertionSize}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"contentAssetStorageAddress\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{contentAssetStorageAddress}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"firstAssertionId\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{firstAssertionId}}\"\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"key\": \"hashFunctionId\",\n\t\t\t\t\t\t\t\t\t\t\t\"value\": \"{{hashFunctionId}}\"\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"OK\",\n\t\t\t\t\t\t\t\"code\": 200,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"39\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"27-ieFm/6t4DZwm0kFCMq71s37uy/g\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:59:02 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"bidSuggestion\\\": \\\"1122511549276000025\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Local Store\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{{assertions}}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/local-store\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"local-store\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Store locally.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Local Store\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{assertions}}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/local-store\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"local-store\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-fpQtTlhbbWO7tqbMGm3CkKmOqaI\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:59:11 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"0a4ee669-95bb-41cd-a2e8-3382361e80d9\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Publish Knowledge Asset\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disabledSystemHeaders\": {\n\t\t\t\t\t\t\t\"content-type\": true\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:belgrade> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'OT' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:1> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/publish\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"publish\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Publish assertion.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Publish Knowledge Asset\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:belgrade> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'OT' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:1> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/publish\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"publish\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Limit\",\n\t\t\t\t\t\t\t\t\t\"value\": \"10\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Remaining\",\n\t\t\t\t\t\t\t\t\t\"value\": \"9\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Reset\",\n\t\t\t\t\t\t\t\t\t\"value\": \"52\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-wKIhHpa0/tdVYh1Y8D2yINolruA\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 13:59:54 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"476fb996-db1a-47b8-8da4-80d71411feb3\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Get Knowledge Asset\",\n\t\t\t\t\t\"protocolProfileBehavior\": {\n\t\t\t\t\t\t\"disabledSystemHeaders\": {}\n\t\t\t\t\t},\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"id\\\": \\\"did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\\\",\\n  \\\"state\\\": \\\"LATEST\\\",\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/get\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"get\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get an assertion.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Get Knowledge Asset\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"id\\\": \\\"did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\\\",\\n  \\\"state\\\": \\\"LATEST\\\",\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/get\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"get\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Limit\",\n\t\t\t\t\t\t\t\t\t\"value\": \"10\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Remaining\",\n\t\t\t\t\t\t\t\t\t\"value\": \"9\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Reset\",\n\t\t\t\t\t\t\t\t\t\"value\": \"44\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-/27PH/ZH74wwVBrYDxWSzCk4yA0\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 14:00:02 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"5b34c048-2d08-4696-b3c4-c37c831b89ce\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Update Knowledge Asset\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:Nis> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'TL' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:2> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/update\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"update\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Update assertion.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Update Knowledge Asset\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"assertionId\\\": \\\"0xef0adc464c3dcb1d353567db5972de8d47f44d6621326645324f9730f2c83cf0\\\",\\n  \\\"assertion\\\": [\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:Nis> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'TL' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:2> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xa3acb6d57097f316b973e9e33d303cf411b8d62d7d589576e348d0d7049e3b63' .\\\"\\n  ],\\n  \\\"blockchain\\\": \\\"hardhat\\\",\\n  \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n  \\\"tokenId\\\": 0,\\n  \\\"hashFunctionId\\\": 1\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/update\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"update\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Limit\",\n\t\t\t\t\t\t\t\t\t\"value\": \"10\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Remaining\",\n\t\t\t\t\t\t\t\t\t\"value\": \"9\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"RateLimit-Reset\",\n\t\t\t\t\t\t\t\t\t\"value\": \"38\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-77qAdgCc/SEN47aETAww86PM04w\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 14:00:09 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"f0d34032-6910-49b4-a2a8-71c9f58feb58\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Query DKG\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"query\\\": \\\"CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9> { ?s ?p ?o . }}}\\\",\\n  \\\"type\\\": \\\"CONSTRUCT\\\",\\n  \\\"repository\\\": \\\"privateCurrent\\\"\\n}\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/query\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\"query\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Execute a query.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"[v0] Query DKG\",\n\t\t\t\t\t\t\t\"originalRequest\": {\n\t\t\t\t\t\t\t\t\"method\": \"POST\",\n\t\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\t\"value\": \"application/json\"\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\"body\": {\n\t\t\t\t\t\t\t\t\t\"mode\": \"raw\",\n\t\t\t\t\t\t\t\t\t\"raw\": \"{\\n  \\\"query\\\": \\\"CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9> { ?s ?p ?o . }}}\\\",\\n  \\\"type\\\": \\\"CONSTRUCT\\\",\\n  \\\"repository\\\": \\\"privateCurrent\\\"\\n}\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/v0/query\",\n\t\t\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\t\t\"v0\",\n\t\t\t\t\t\t\t\t\t\t\"query\"\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\"status\": \"Accepted\",\n\t\t\t\t\t\t\t\"code\": 202,\n\t\t\t\t\t\t\t\"_postman_previewlanguage\": \"json\",\n\t\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"X-Powered-By\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Express\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Access-Control-Allow-Origin\",\n\t\t\t\t\t\t\t\t\t\"value\": \"*\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Type\",\n\t\t\t\t\t\t\t\t\t\"value\": \"application/json; charset=utf-8\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Content-Length\",\n\t\t\t\t\t\t\t\t\t\"value\": \"54\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"ETag\",\n\t\t\t\t\t\t\t\t\t\"value\": \"W/\\\"36-kqaRe64EoJygoEadXJWRekiCs4s\\\"\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Date\",\n\t\t\t\t\t\t\t\t\t\"value\": \"Thu, 17 Aug 2023 14:00:19 GMT\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Connection\",\n\t\t\t\t\t\t\t\t\t\"value\": \"keep-alive\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"key\": \"Keep-Alive\",\n\t\t\t\t\t\t\t\t\t\"value\": \"timeout=5\"\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"cookie\": [],\n\t\t\t\t\t\t\t\"body\": \"{\\n    \\\"operationId\\\": \\\"4d371ffb-a620-452f-8d16-3e427bafeae2\\\"\\n}\"\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"[v0] Get Operation Result\",\n\t\t\t\t\t\"request\": {\n\t\t\t\t\t\t\"method\": \"GET\",\n\t\t\t\t\t\t\"header\": [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\"key\": \"Authorization\",\n\t\t\t\t\t\t\t\t\"value\": \"Bearer {{authToken}}\"\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t],\n\t\t\t\t\t\t\"url\": {\n\t\t\t\t\t\t\t\"raw\": \"{{host}}:{{port}}/{{operation}}/{{operationId}}\",\n\t\t\t\t\t\t\t\"host\": [\n\t\t\t\t\t\t\t\t\"{{host}}\"\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\t\"port\": \"{{port}}\",\n\t\t\t\t\t\t\t\"path\": [\n\t\t\t\t\t\t\t\t\"{{operation}}\",\n\t\t\t\t\t\t\t\t\"{{operationId}}\"\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"description\": \"Get result of a specific operation by its ID.\"\n\t\t\t\t\t},\n\t\t\t\t\t\"response\": []\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t],\n\t\"auth\": {\n\t\t\"type\": \"bearer\",\n\t\t\"bearer\": [\n\t\t\t{\n\t\t\t\t\"key\": \"token\",\n\t\t\t\t\"value\": \"{{authToken}}\",\n\t\t\t\t\"type\": \"string\"\n\t\t\t}\n\t\t]\n\t},\n\t\"event\": [\n\t\t{\n\t\t\t\"listen\": \"prerequest\",\n\t\t\t\"script\": {\n\t\t\t\t\"type\": \"text/javascript\",\n\t\t\t\t\"exec\": [\n\t\t\t\t\t\"\"\n\t\t\t\t]\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"listen\": \"test\",\n\t\t\t\"script\": {\n\t\t\t\t\"type\": \"text/javascript\",\n\t\t\t\t\"exec\": [\n\t\t\t\t\t\"\"\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t],\n\t\"variable\": [\n\t\t{\n\t\t\t\"key\": \"host\",\n\t\t\t\"value\": \"localhost\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"port\",\n\t\t\t\"value\": \"8900\",\n\t\t\t\"type\": \"number\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"authToken\",\n\t\t\t\"value\": \"\",\n\t\t\t\"type\": \"string\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"blockchain\",\n\t\t\t\"value\": \"hardhat\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"epochsNumber\",\n\t\t\t\"value\": \"5\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"assertionSize\",\n\t\t\t\"value\": \"299\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"contentAssetStorageAddress\",\n\t\t\t\"value\": \"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"hashFunctionId\",\n\t\t\t\"value\": \"1\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"firstAssertionId\",\n\t\t\t\"value\": \"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"assertions\",\n\t\t\t\"value\": \"[\\n  {\\n    \\\"blockchain\\\": \\\"hardhat\\\",\\n    \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n    \\\"tokenId\\\": 0,\\n    \\\"assertionId\\\": \\\"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\\\",\\n    \\\"assertion\\\": [\\n      \\\"<uuid:1> <http://schema.org/city> <uuid:belgrade> .\\\",\\n      \\\"<uuid:1> <http://schema.org/company> 'OT' .\\\",\\n      \\\"<uuid:1> <http://schema.org/user> <uuid:user:1> .\\\",\\n      \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9' .\\\"\\n    ],\\n    \\\"storeType\\\": \\\"TRIPLE\\\"\\n  },\\n  {\\n    \\\"blockchain\\\": \\\"hardhat\\\",\\n    \\\"contract\\\": \\\"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\\\",\\n    \\\"tokenId\\\": 0,\\n    \\\"assertionId\\\": \\\"0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9\\\",\\n    \\\"assertion\\\": [\\n      \\\"<uuid:belgrade> <http://schema.org/postCode> '11000' .\\\",\\n      \\\"<uuid:belgrade> <http://schema.org/title> 'Belgrade' .\\\",\\n      \\\"<uuid:user:1> <http://schema.org/lastname> 'Smith' .\\\",\\n      \\\"<uuid:user:1> <http://schema.org/name> 'Adam' .\\\"\\n    ],\\n    \\\"storeType\\\": \\\"TRIPLE\\\"\\n  }\\n]\",\n\t\t\t\"type\": \"string\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"assertionId\",\n\t\t\t\"value\": \"0xe3a6733d7b999ca6f0d141afe3e38ac59223a4dfde7a5458932d2094ed4193cf\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"assertion\",\n\t\t\t\"value\": \"[\\n    \\\"<uuid:1> <http://schema.org/city> <uuid:belgrade> .\\\",\\n    \\\"<uuid:1> <http://schema.org/company> 'OT' .\\\",\\n    \\\"<uuid:1> <http://schema.org/user> <uuid:user:1> .\\\",\\n    \\\"_:c14n0 <https://ontology.origintrail.io/dkg/1.0#privateAssertionID> '0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9' .\\\"\\n]\",\n\t\t\t\"type\": \"string\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"contract\",\n\t\t\t\"value\": \"0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"tokenId\",\n\t\t\t\"value\": \"1\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"UAL\",\n\t\t\t\"value\": \"did:dkg:hardhat/0xb0d4afd8879ed9f52b28595d31b441d079b2ca07/0\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"state\",\n\t\t\t\"value\": \"LATEST\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"query\",\n\t\t\t\"value\": \"CONSTRUCT { ?s ?p ?o } WHERE {{GRAPH <assertion:0xcfab2d364fe01757d7a83d3b32284395d87b1c379adabb1e28a16666e0a4fca9> { ?s ?p ?o . }}}\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"type\",\n\t\t\t\"value\": \"CONSTRUCT\"\n\t\t},\n\t\t{\n\t\t\t\"key\": \"repository\",\n\t\t\t\"value\": \"privateCurrent\"\n\t\t}\n\t]\n}"
  },
  {
    "path": "index.js",
    "content": "/* eslint-disable no-console */\nimport 'dotenv/config';\nimport fs from 'fs-extra';\nimport OTNode from './ot-node.js';\nimport { NODE_ENVIRONMENTS } from './src/constants/constants.js';\n\nprocess.env.NODE_ENV =\n    process.env.NODE_ENV && Object.values(NODE_ENVIRONMENTS).includes(process.env.NODE_ENV)\n        ? process.env.NODE_ENV\n        : NODE_ENVIRONMENTS.DEVELOPMENT;\n\n(async () => {\n    let userConfig = null;\n    try {\n        if (process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT && process.argv.length === 3) {\n            const configurationFilename = process.argv[2];\n            userConfig = JSON.parse(await fs.promises.readFile(process.argv[2]));\n            userConfig.configFilename = configurationFilename;\n        }\n    } catch (error) {\n        console.log('Unable to read user configuration from file: ', process.argv[2]);\n        process.exit(1);\n    }\n    try {\n        const node = new OTNode(userConfig);\n        await node.start();\n    } catch (e) {\n        console.error(`Error occurred while start ot-node, error message: ${e}. ${e.stack}`);\n        // console.error(`Trying to recover from older version`);\n        // if (process.env.NODE_ENV !== NODE_ENVIRONMENTS.DEVELOPMENT) {\n        //     const rootPath = path.join(appRootPath.path, '..');\n        //     const oldVersionsDirs = (await fs.promises.readdir(rootPath, { withFileTypes: true }))\n        //         .filter((dirent) => dirent.isDirectory())\n        //         .map((dirent) => dirent.name)\n        //         .filter((name) => semver.valid(name) && !appRootPath.path.includes(name));\n        //\n        //     if (oldVersionsDirs.length === 0) {\n        //         console.error(\n        //             `Failed to start OT-Node, no backup code available. Error message: ${e.message}`,\n        //         );\n        //         process.exit(1);\n        //     }\n        //\n        //     const oldVersion = oldVersionsDirs.sort(semver.compare).pop();\n        //     const oldversionPath = path.join(rootPath, oldVersion);\n        //     execSync(`ln -sfn ${oldversionPath} ${rootPath}/current`);\n        //     await fs.promises.rm(appRootPath.path, { force: true, recursive: true });\n        // }\n        process.exit(1);\n    }\n})();\n\nprocess.on('unhandledRejection', (err) => {\n    // Handle specific libp2p peer lookup failures that escape try-catch blocks\n    if (err && err.code === 'ERR_LOOKUP_FAILED') {\n        console.warn(`Peer lookup failed (ERR_LOOKUP_FAILED): ${err.message}`);\n        return; // Don't crash for peer lookup failures\n    }\n\n    // Handle ECONNRESET errors gracefully - these are common network issues\n    if (err && (err.code === 'ECONNRESET' || err.errno === -104)) {\n        console.warn(`Network connection reset (ECONNRESET): ${err.message}`);\n        return; // Don't crash for connection reset errors\n    }\n\n    // Handle ERR_UNSUPPORTED_PROTOCOL errors gracefully\n    if (err && err.code === 'ERR_UNSUPPORTED_PROTOCOL') {\n        console.warn(`Unsupported protocol error (ERR_UNSUPPORTED_PROTOCOL): ${err.message}`);\n        return; // Don't crash for protocol errors\n    }\n\n    // Handle EPIPE (broken pipe) errors gracefully\n    if (err && (err.code === 'EPIPE' || err.errno === -32)) {\n        console.warn(`Broken pipe error (EPIPE): ${err.message}`);\n        return; // Don't crash for broken pipe errors\n    }\n\n    // Handle ETIMEDOUT errors gracefully - these are common database connection timeouts\n    if (err && (err.code === 'ETIMEDOUT' || err.errno === -110)) {\n        console.warn(`Connection timeout error (ETIMEDOUT): ${err.message}`);\n        return; // Don't crash for timeout errors\n    }\n\n    // Handle Sequelize \"Got timeout reading communication packets\" errors gracefully\n    if (err && err.message && err.message.includes('Got timeout reading communication packets')) {\n        console.warn(`Sequelize communication timeout error: ${err.message}`);\n        return; // Don't crash for database communication timeout errors\n    }\n\n    // For all other unhandled rejections, crash the node\n    console.error('Something went really wrong! OT-node shutting down...', err);\n    process.exit(1);\n});\n\nprocess.on('uncaughtException', (err) => {\n    // Handle ERR_UNSUPPORTED_PROTOCOL errors gracefully\n    if (err && err.code === 'ERR_UNSUPPORTED_PROTOCOL') {\n        console.warn(`Unsupported protocol error (ERR_UNSUPPORTED_PROTOCOL): ${err.message}`);\n        return; // Don't crash for protocol errors\n    }\n\n    // Handle EPIPE (broken pipe) errors gracefully\n    if (err && (err.code === 'EPIPE' || err.errno === -32)) {\n        console.warn(`Broken pipe error (EPIPE): ${err.message}`);\n        return; // Don't crash for broken pipe errors\n    }\n\n    // Handle ECONNRESET errors gracefully\n    if (err && (err.code === 'ECONNRESET' || err.errno === -104)) {\n        console.warn(`Network connection reset (ECONNRESET): ${err.message}`);\n        return; // Don't crash for connection reset errors\n    }\n\n    // Handle ETIMEDOUT errors gracefully - these are common database connection timeouts\n    if (err && (err.code === 'ETIMEDOUT' || err.errno === -110)) {\n        console.warn(`Connection timeout error (ETIMEDOUT): ${err.message}`);\n        return; // Don't crash for timeout errors\n    }\n\n    // Handle Sequelize \"Got timeout reading communication packets\" errors gracefully\n    if (err && err.message && err.message.includes('Got timeout reading communication packets')) {\n        console.warn(`Sequelize communication timeout error: ${err.message}`);\n        return; // Don't crash for database communication timeout errors\n    }\n\n    console.error('Something went really wrong! OT-node shutting down...', err);\n    process.exit(1);\n});\n"
  },
  {
    "path": "installer/README.md",
    "content": "Installs the V8 Node\n\n2. Login to the server as root. You __cannot__ use sudo and run this script. The command \"npm install\" __will__ fail.\n\n3. Execute the following command:\n\n```\ncd /root/ && curl https://raw.githubusercontent.com/OriginTrail/ot-node/v8/release/testnet/installer/installer.sh --output installer.sh && chmod +x installer.sh\n```\n\n"
  },
  {
    "path": "installer/installer.sh",
    "content": "#!/bin/bash\n\nOTNODE_DIR=\"/root/ot-node\"\n\ntext_color() {\n    GREEN='\\033[0;32m'\n    BGREEN='\\033[1;32m'\n    RED='\\033[0;31m'\n    BRED='\\033[1;31m'\n    YELLOW='\\033[0;33m'\n    BYELLOW='\\033[1;33m'\n    BOLD='\\033[1m'\n    NC='\\033[0m' # No Color\n    \n    # Detect if this is an error, warning or success message\n    local message=\"$@\"\n    if [[ \"$message\" == *\"$RED\"* || \"$message\" == *\"$BRED\"* ]]; then\n        echo -e \"❌ $@$NC\"\n    elif [[ \"$message\" == *\"$YELLOW\"* || \"$message\" == *\"$BYELLOW\"* ]]; then\n        echo -e \"⚠️  $@$NC\"\n    elif [[ \"$message\" == *\"$GREEN\"* || \"$message\" == *\"$BGREEN\"* ]]; then\n        echo -e \"✅ $@$NC\"\n    else\n        echo -e \"$@$NC\"\n    fi\n}\n\nheader_color() {\n    LIGHTCYAN='\\033[1;36m'\n    NC='\\033[0m' # No Color\n    local header_text=\"$@\"\n    local line=$(printf '═%.0s' $(seq 1 ${#header_text}))\n    \n    echo \"\"\n    echo -e \"${LIGHTCYAN}╔═${line}═╗${NC}\"\n    echo -e \"${LIGHTCYAN}║ ${header_text} ║${NC}\"\n    echo -e \"${LIGHTCYAN}╚═${line}═╝${NC}\"\n    echo \"\"\n}\n\nperform_step() {\n    N1=$'\\n'\n    echo -n \"⏳ ${@: -1}: \"\n\n    OUTPUT=$(${@:1:$#-1} 2>&1)\n\n    if [[ $? -ne 0 ]]; then\n        text_color $BOLD$RED \"FAILED\"\n        echo -e \"${N1}❌ Step failed. Output of error is:${N1}${N1}$OUTPUT\"\n        echo -e \"${BRED}Press Enter to exit the installer.${NC}\"\n        read\n        exit 1\n    else\n        text_color $BOLD$GREEN \"OK\"\n    fi\n}\n\n# Function to display a notification box\nnotification_box() {\n    local message=\"$1\"\n    local type=\"$2\"\n    local RED='\\033[0;31m'\n    local GREEN='\\033[0;32m'\n    local YELLOW='\\033[0;33m'\n    local BLUE='\\033[0;34m'\n    local BOLD='\\033[1m'\n    local NC='\\033[0m'\n    \n    local color=\"$BLUE\"\n    local icon=\"ℹ️\"\n    \n    if [[ \"$type\" == \"error\" ]]; then\n        color=\"$RED\"\n        icon=\"❌\"\n    elif [[ \"$type\" == \"warning\" ]]; then\n        color=\"$YELLOW\"\n        icon=\"⚠️\"\n    elif [[ \"$type\" == \"success\" ]]; then\n        color=\"$GREEN\"\n        icon=\"✅\"\n    fi\n    \n    local line=$(printf '─%.0s' $(seq 1 60))\n    echo -e \"${color}$line${NC}\"\n    echo -e \"${color}${BOLD} $icon $message${NC}\"\n    echo -e \"${color}$line${NC}\"\n    \n    if [[ \"$type\" == \"error\" ]]; then\n        echo -e \"${BRED}Press Enter to exit the installer.${NC}\"\n        read\n    fi\n}\n\n# Check Ubuntu version\ncheck_ubuntu_version() {\n    UBUNTU_VERSION=$(lsb_release -r -s)\n\n    if [[ \"$UBUNTU_VERSION\" != \"20.04\" && \"$UBUNTU_VERSION\" != \"22.04\" && \"$UBUNTU_VERSION\" != \"24.04\" ]]; then\n        notification_box \"Error: OriginTrail node installer currently requires Ubuntu 20.04 LTS, 22.04 LTS or 24.04 LTS versions in order to execute successfully. You are installing on Ubuntu $UBUNTU_VERSION.\"\n        echo -e \"${BRED}Please make sure that you get familiar with the requirements before setting up your OriginTrail node! Documentation: docs.origintrail.io${NC}\"\n        exit 1\n    fi\n}\n\n# Check if script is running as root\ncheck_root() {\n    if [[ $EUID -ne 0 ]]; then\n        notification_box \"Error: This script must be run as root.\"\n        echo -e \"${BRED}Please re-run the script as root using 'sudo'.${NC}\"\n        exit 1\n    fi\n}\n\ninstall_aliases() {\n    if [[ -f \"/root/.bashrc\" ]]; then\n        if grep -Fxq \"alias otnode-restart='systemctl restart otnode.service'\" ~/.bashrc; then\n            echo \"Aliases found, skipping.\"\n        else\n            echo \"alias otnode-restart='systemctl restart otnode.service'\" >> ~/.bashrc\n            echo \"alias otnode-stop='systemctl stop otnode.service'\" >> ~/.bashrc\n            echo \"alias otnode-start='systemctl start otnode.service'\" >> ~/.bashrc\n            echo \"alias otnode-logs='journalctl -u otnode --output cat -f'\" >> ~/.bashrc\n            echo \"alias otnode-config='nano ~/ot-node/.origintrail_noderc'\" >> ~/.bashrc\n        fi\n    else\n        echo \"bashrc does not exist. Proceeding with OriginTrail node installation.\"\n    fi\n}\n\ninstall_directory() {\n    ARCHIVE_REPOSITORY_URL=\"github.com/OriginTrail/ot-node/archive\"\n    \n    echo \"\"\n    echo -e \"${CYAN}┌─────────────────────────────────────────────┐${RESET}\"\n    echo -e \"${CYAN}│     NODE ENVIRONMENT SELECTION              │${RESET}\"\n    echo -e \"${CYAN}└─────────────────────────────────────────────┘${RESET}\"\n    echo \"\"\n    echo -e \"Please select the environment for your OriginTrail node:\"\n    echo -e \"  [M] ${GREEN}Mainnet${RESET} - Production environment\"\n    echo -e \"  [T] ${YELLOW}Testnet${RESET} - Testing environment\"\n    echo \"\"\n    read -p \"▶ Your choice [M/T/E to exit]: \" choice\n\n    case \"$choice\" in\n        [tT]* ) nodeEnv=\"testnet\"; BRANCH=\"v6/release/testnet\"; BRANCH_DIR=\"/root/ot-node-6-release-testnet\";;\n        [mM]* ) nodeEnv=\"mainnet\"; BRANCH=\"v6/release/mainnet\"; BRANCH_DIR=\"/root/ot-node-6-release-mainnet\";;\n        [eE]* ) text_color $RED \"Installer stopped by user\"; exit;;\n        * ) nodeEnv=\"mainnet\"; BRANCH=\"v6/release/mainnet\"; BRANCH_DIR=\"/root/ot-node-6-release-mainnet\";;\n    esac\n    \n    text_color $GREEN \"Selected environment: $nodeEnv with branch: $BRANCH\"\n\n    perform_step wget https://$ARCHIVE_REPOSITORY_URL/$BRANCH.zip \"Downloading node files\"\n    perform_step unzip *.zip \"Unzipping node files\"\n    perform_step rm *.zip \"Removing zip file\"\n    OTNODE_VERSION=$(jq -r '.version' $BRANCH_DIR/package.json)\n    perform_step mkdir $OTNODE_DIR \"Creating new ot-node directory\"\n    perform_step mkdir $OTNODE_DIR/$OTNODE_VERSION \"Creating new ot-node version directory\"\n    perform_step mv $BRANCH_DIR/* $OTNODE_DIR/$OTNODE_VERSION/ \"Moving downloaded node files to ot-node version directory\"\n    OUTPUT=$(mv $BRANCH_DIR/.* $OTNODE_DIR/$OTNODE_VERSION/ 2>&1)\n    perform_step rm -rf $BRANCH_DIR \"Removing old directories\"\n    perform_step ln -sfn $OTNODE_DIR/$OTNODE_VERSION $OTNODE_DIR/current \"Creating symlink from $OTNODE_DIR/$OTNODE_VERSION to $OTNODE_DIR/current\"\n    echo \"NODE_ENV=$nodeEnv\" >> $OTNODE_DIR/current/.env\n    # Save selected environment for later use\n    export SELECTED_NODE_ENV=$nodeEnv\n}\n\n\ninstall_prereqs() {\n    export DEBIAN_FRONTEND=noninteractive\n    NODEJS_VER=\"20\"\n\n    perform_step install_aliases \"Updating .bashrc file with OriginTrail node aliases\" > /dev/null 2>&1\n    perform_step rm -rf /var/lib/dpkg/lock-frontend \"Removing any frontend locks\" > /dev/null 2>&1\n    perform_step apt update \"Updating Ubuntu package repository\" > /dev/null 2>&1\n    perform_step apt upgrade -y \"Updating Ubuntu to the latest version\" > /dev/null 2>&1\n    perform_step apt install unzip jq -y \"Installing unzip, jq\" > /dev/null 2>&1\n    perform_step apt install default-jre -y \"Installing default-jre\" > /dev/null 2>&1\n    perform_step apt install build-essential -y \"Installing build-essential\" > /dev/null 2>&1\n\n    # Install nodejs 20 (via NVM).\n    wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash > /dev/null 2>&1\n    export NVM_DIR=\"$HOME/.nvm\"\n    # This loads nvm\n    [ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\"\n    # This loads nvm bash_completion\n    [ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\"\n    nvm install $NODEJS_VER > /dev/null 2>&1\n    nvm use $NODEJS_VER > /dev/null 2>&1\n\n    # Set nodejs 20 as default and link node to /usr/bin/\n    nvm alias default $NODEJS_VER > /dev/null 2>&1\n    sudo ln -s $(which node) /usr/bin/ > /dev/null 2>&1\n    sudo ln -s $(which npm) /usr/bin/ > /dev/null 2>&1\n\n    apt remove unattended-upgrades -y > /dev/null 2>&1\n\n    perform_step apt remove unattended-upgrades -y \"Remove unattended upgrades\" > /dev/null 2>&1\n}\n\n\ninstall_fuseki() {\n    FUSEKI_VER=\"apache-jena-fuseki-$(git ls-remote --tags https://github.com/apache/jena | grep -o 'refs/tags/jena-[0-9]*\\.[0-9]*\\.[0-9]*' | sort -r | head -n 1 | grep -o '[^\\/-]*$')\"\n    FUSEKI_PREV_VER=\"apache-jena-fuseki-$(git ls-remote --tags https://github.com/apache/jena | grep -o 'refs/tags/jena-[0-9]*\\.[0-9]*\\.[0-9]*' | sort -r | head -n 3 | tail -n 1 | grep -o '[^\\/-]*$')\"\n    wget -q --spider https://dlcdn.apache.org/jena/binaries/$FUSEKI_VER.zip\n    if [[ $? -ne 0 ]]; then\n        FUSEKI_VER=$FUSEKI_PREV_VER\n    fi\n\n    perform_step wget https://dlcdn.apache.org/jena/binaries/$FUSEKI_VER.zip \"Downloading Fuseki\"\n    perform_step unzip $FUSEKI_VER.zip \"Unzipping Fuseki\"\n    perform_step rm /root/$FUSEKI_VER.zip \"Removing Fuseki zip file\"\n    perform_step mkdir /root/ot-node/fuseki \"Making /root/ot-node/fuseki directory\"\n    perform_step cp /root/$FUSEKI_VER/fuseki-server.jar /root/ot-node/fuseki/ \"Copying Fuseki files to $OTNODE_DIR/fuseki/ 1/2\"\n    perform_step cp -r /root/$FUSEKI_VER/webapp/ /root/ot-node/fuseki/ \"Copying Fuseki files to $OTNODE_DIR/fuseki/ 1/2\"\n    perform_step rm -r /root/$FUSEKI_VER \"Removing the remaining /root/$FUSEKI_VER directory\"\n    perform_step cp $OTNODE_DIR/installer/data/fuseki.service /lib/systemd/system/ \"Copying Fuseki service file\"\n    systemctl daemon-reload\n    perform_step systemctl enable fuseki \"Enabling Fuseki\"\n    perform_step systemctl start fuseki \"Starting Fuseki\"\n    perform_step systemctl status fuseki \"Fuseki status\"\n}\n\ninstall_blazegraph() {\n    perform_step wget https://github.com/blazegraph/database/releases/latest/download/blazegraph.jar \"Downloading Blazegraph\"\n    perform_step cp $OTNODE_DIR/installer/data/blazegraph.service /lib/systemd/system/ \"Copying Blazegraph service file\"\n    mv blazegraph.jar $OTNODE_DIR/../blazegraph.jar\n    systemctl daemon-reload\n    perform_step systemctl enable blazegraph \"Enabling Blazegrpah\"\n    perform_step systemctl start blazegraph \"Starting Blazegraph\"\n    perform_step systemctl status blazegraph \"Blazegraph status\"\n}\n\ninstall_sql() {\n    # Replace the SQL database selection with a more user-friendly interface\n    text_color $BYELLOW \"╔════════════════════════════════════════════════════════════════╗\"\n    text_color $BYELLOW \"║  IMPORTANT: SQL Database Selection                             ║\"\n    text_color $BYELLOW \"╚════════════════════════════════════════════════════════════════╝\"\n    text_color $YELLOW \"  To avoid potential migration issues, please select the SQL type\"\n    text_color $YELLOW \"  you are currently using. For first installations, both choices\"\n    text_color $YELLOW \"  are valid. If unsure, select option [1].\"\n    echo \"\"\n\n    while true; do\n        echo -e \"${CYAN}Available SQL database options:${RESET}\"\n        echo -e \"  [1] ${GREEN}MySQL${RESET}   - Default choice\"\n        echo -e \"  [2] ${GREEN}MariaDB${RESET} - Alternative option\"\n        echo -e \"  [E] ${RED}Exit${RESET}    - Cancel installation\"\n        echo \"\"\n        read -p \"▶ Your choice: \" choice\n        case \"$choice\" in\n            [2]* )  text_color $GREEN \"✅ MariaDB selected. Proceeding with installation.\"\n                    sql=mariadb\n                    perform_step apt-get install curl software-properties-common dirmngr ca-certificates apt-transport-https -y \"Installing mariadb dependencies\"\n                    curl -LsS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash -s -- --mariadb-server-version=10.8\n                    perform_step apt-get install mariadb-server -y \"Installing mariadb-server\"\n                    break;;\n            [Ee]* ) text_color $RED \"❌ Installer stopped by user\"; exit;;\n            * )     text_color $GREEN \"✅ MySQL selected. Proceeding with installation.\"\n                    sql=mysql\n                    mysql_native_password=\" WITH mysql_native_password\"\n                    perform_step apt-get install tcllib mysql-server -y \"Installing mysql-server\"\n                    break;;\n        esac\n    done\n\n    #check old sql password\n    OUTPUT=$($sql -u root -e \"status;\" 2>&1)\n    if [[ $? -ne 0 ]]; then\n        while true; do\n            read -s -p \"Enter your old sql password: \" oldpassword\n            echo\n            echo -n \"Password check: \"\n            OUTPUT=$(MYSQL_PWD=$oldpassword $sql -u root -e \"status;\" 2>&1)\n            if [[ $? -ne 0 ]]; then\n                text_color $YELLOW\"ERROR - The sql repository password provided does not match your sql password. Please try again.\"\n            else\n                text_color $GREEN \"OK\"\n                break\n            fi\n        done\n    fi\n\n    #check operationaldb\n    if [[ -d \"/var/lib/mysql/operationaldb/\" ]]; then\n        read -p \"Old operationaldb repository detected. Would you like to overwrite it ? (Default: No) [Y]es [N]o [E]xit \" choice\n        case \"$choice\" in\n            [yY]* ) perform_step $(MYSQL_PWD=$oldpassword $sql -u root -e \"DROP DATABASE IF EXISTS operationaldb;\") \"Overwritting slq repository\";;\n            [eE]* ) text_color $RED\"Installer stopped by user\"; exit;;\n            * )     text_color $GREEN\"Keeping previous sql repository\"; NEW_DB=FALSE;;\n        esac\n    fi\n\n    #check sql new password\n    read -p \"Would you like to change your sql password or add one ? (Default: Yes) [Y]es [N]o [E]xit \" choice\n    case \"$choice\" in\n        [nN]* ) text_color $GREEN\"Keeping previous sql password\"; password=$oldpassword;;\n        [eE]* ) text_color $RED\"Installer stopped by user\"; exit;;\n        * )     while true; do\n                    read -s -p \"Enter your new sql password: \" password\n                    echo\n                    read -s -p \"Please confirm your new sql password: \" password2\n                    echo\n                    [[ $password = $password2 ]] && break\n                    text_color $YELLOW \"Password entered do not match. Please try again.\"\n                done\n                perform_step $(MYSQL_PWD=$oldpassword $sql -u root -e \"ALTER USER 'root'@'localhost' IDENTIFIED$mysql_native_password BY '$password';\") \"Changing sql password\";;\n    esac\n\n    perform_step $(echo \"REPOSITORY_PASSWORD=$password\" >> $OTNODE_DIR/.env) \"Adding sql password to .env\"\n    if [[ $NEW_DB != FALSE ]]; then\n        perform_step $(MYSQL_PWD=$password $sql -u root -e \"CREATE DATABASE operationaldb /*\\!40100 DEFAULT CHARACTER SET utf8 */;\") \"Creating new sql repository\"\n    fi\n    if [[ $sql = mysql ]]; then\n        perform_step sed -i 's|max_binlog_size|#max_binlog_size|' /etc/mysql/mysql.conf.d/mysqld.cnf \"Setting max log size\"\n        perform_step $(echo -e \"disable_log_bin\\nwait_timeout = 31536000\\ninteractive_timeout = 31536000\" >> /etc/mysql/mysql.conf.d/mysqld.cnf) \"Adding disable_log_bin, wait_timeout, interactive_timeout to sql config\"\n    fi\n    if [[ $sql = mariadb ]]; then\n        perform_step sed -i 's|max_binlog_size|#max_binlog_size|' /etc/mysql/mariadb.conf.d/50-server.cnf \"Setting max log size\"\n        perform_step $(echo -e \"disable_log_bin\\nwait_timeout = 31536000\\ninteractive_timeout = 31536000\" >> /etc/mysql/mariadb.conf.d/50-server.cnf) \"Adding disable_log_bin, wait_timeout, interactive_timeout to sql config\"\n    fi\n    perform_step systemctl restart $sql \"Restarting $sql\"\n}\n\n# Define wallet configuration functions\nrequest_operational_wallet_keys() {\n    WALLET_ADDRESSES=()\n    WALLET_PRIVATE_KEYS=()\n\n    echo \"\"\n    echo -e \"${CYAN}┌─────────────────────────────────────────────────────────┐${RESET}\"\n    echo -e \"${CYAN}│     OPERATIONAL WALLET CONFIGURATION                    │${RESET}\"\n    echo -e \"${CYAN}└─────────────────────────────────────────────────────────┘${RESET}\"\n    echo \"\"\n    echo -e \"${YELLOW}You'll now be asked to input your operational wallets for $1.${RESET}\"\n    echo -e \"${YELLOW}(Press ENTER without typing to skip/finish adding wallets)${RESET}\"\n    echo \"\"\n    \n    wallet_no=1\n    while true; do\n        echo -e \"${CYAN}=== Wallet #$wallet_no Configuration ===${RESET}\"\n        read -p \"▶ Address for $1 operational wallet #$wallet_no: \" address\n        [[ -z $address ]] && break\n        text_color $GREEN \"✅ EVM operational wallet address for $blockchain wallet #$wallet_no: $address\"\n\n        read -s -p \"▶ Private key for $1 operational wallet #$wallet_no: \" private_key\n        echo  # Add newline after hidden input\n        [[ -z $private_key ]] && break\n        text_color $GREEN \"✅ EVM operational wallet private key stored successfully!\"\n\n        WALLET_ADDRESSES+=($address)\n        WALLET_PRIVATE_KEYS+=($private_key)\n        wallet_no=$((wallet_no + 1))\n        echo \"\"\n    done\n\n    OP_WALLET_KEYS_JSON=$(jq -n '\n        [\n        $ARGS.positional as $args\n        | ($args | length / 2) as $upto\n        | range(0; $upto) as $start\n        | [{ evmAddress: $args[$start], privateKey: $args[$start + $upto] }]\n        ] | add\n        ' --args \"${WALLET_ADDRESSES[@]}\" \"${WALLET_PRIVATE_KEYS[@]}\")\n    \n    echo -e \"${GREEN}✅ Wallet configuration completed${RESET}\"\n}\n\n# Enhanced validate_operator_fees function with better UI\nvalidate_operator_fees() {\n    local blockchain=$1\n    echo \"\"\n    echo -e \"${CYAN}┌─────────────────────────────────────────────┐${RESET}\"\n    echo -e \"${CYAN}│     OPERATOR FEE CONFIGURATION              │${RESET}\"\n    echo -e \"${CYAN}└─────────────────────────────────────────────┘${RESET}\"\n    echo \"\"\n    echo -e \"${YELLOW}The operator fee is the percentage of rewards you will receive (0-100).${RESET}\"\n    \n    while true; do\n        read -p \"▶ Enter operator fee for $blockchain: \" OPERATOR_FEE\n        if [[ \"$OPERATOR_FEE\" =~ ^[0-9]+$ ]] && [ \"$OPERATOR_FEE\" -ge 0 ] && [ \"$OPERATOR_FEE\" -le 100 ]; then\n            print_color $GREEN \"✅ Operator fee for $blockchain set to: $OPERATOR_FEE%\"\n            break\n        else\n            print_color $RED \"⚠️  Invalid input. Please enter a number between 0 and 100.\"\n        fi\n    done\n}\n\n# Define color codes\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nBLUE='\\033[0;34m'\nMAGENTA='\\033[0;35m'\nCYAN='\\033[0;36m'\nRESET='\\033[0m'\n\n# Function to print colored text\nprint_color() {\n    local color=$1\n    local text=$2\n    echo -e \"${color}${text}${RESET}\"\n}\n\ninstall_node() {\n    # Change directory to ot-node/current\n    cd $OTNODE_DIR\n\n    # Set blockchain options based on the selected environment\n    if [ \"$SELECTED_NODE_ENV\" == \"mainnet\" ]; then\n        blockchain_options=(\"Neuroweb\" \"Gnosis\" \"Base\")\n        otp_blockchain_id=2043\n        gnosis_blockchain_id=100\n        base_blockchain_id=8453\n    else\n        blockchain_options=(\"Neuroweb\" \"Gnosis\" \"Base-Sepolia\")\n        otp_blockchain_id=20430\n        gnosis_blockchain_id=10200\n        base_blockchain_id=84532\n    fi\n\n    # Ask user which blockchains to connect to\n    selected_blockchains=()\n    checkbox_states=()\n    for _ in \"${blockchain_options[@]}\"; do\n        checkbox_states+=(\"[ ]\")\n    done\n\n    while true; do\n        clear  # Clear the screen for a cleaner display\n        echo \"\"\n        echo -e \"${CYAN}┌─────────────────────────────────────────────┐${RESET}\"\n        echo -e \"${CYAN}│     BLOCKCHAIN SELECTION                    │${RESET}\"\n        echo -e \"${CYAN}└─────────────────────────────────────────────┘${RESET}\"\n        echo \"\"\n        echo -e \"Please select the blockchains you want to connect your node to:\"\n        echo \"\"\n        for i in \"${!blockchain_options[@]}\"; do\n            echo -e \"    ${checkbox_states[$i]} $((i+1)). ${blockchain_options[$i]}\"\n        done\n        echo -e \"    [ ] $((${#blockchain_options[@]}+1)). All Blockchains\"\n        echo \"\"\n        echo -e \"${YELLOW}Enter the number to toggle selection, or 'd' to finish.${RESET}\"\n        echo \"\"\n        \n        # Use read -n 1 to read a single character without requiring Enter\n        read -n 1 -p \"▶ Your choice: \" choice\n        echo  # Add a newline after the selection\n\n        if [[ \"$choice\" == \"d\" ]]; then\n            if [ ${#selected_blockchains[@]} -eq 0 ]; then\n                echo \"\"\n                print_color $RED \"You must select at least one blockchain. Please try again.\"\n                read -n 1 -p \"Press any key to continue...\"\n                continue\n            else\n                break\n            fi\n        elif [[ \"$choice\" =~ ^[1-${#blockchain_options[@]}]$ ]]; then\n            index=$((choice-1))\n            if [[ \"${checkbox_states[$index]}\" == \"[ ]\" ]]; then\n                checkbox_states[$index]=\"[x]\"\n                selected_blockchains+=(\"${blockchain_options[$index]}\")\n            else\n                checkbox_states[$index]=\"[ ]\"\n                selected_blockchains=(${selected_blockchains[@]/${blockchain_options[$index]}})\n            fi\n        elif [[ \"$choice\" == \"$((${#blockchain_options[@]}+1))\" ]]; then\n            if [[ \"${checkbox_states[-1]}\" == \"[ ]\" ]]; then\n                for i in \"${!checkbox_states[@]}\"; do\n                    checkbox_states[$i]=\"[x]\"\n                done\n                selected_blockchains=(\"${blockchain_options[@]}\")\n            else\n                for i in \"${!checkbox_states[@]}\"; do\n                    checkbox_states[$i]=\"[ ]\"\n                done\n                selected_blockchains=()\n            fi\n        else\n            echo \"\"\n            print_color $RED \"Invalid choice. Please enter a number between 1 and $((${#blockchain_options[@]}+1)).\"\n            read -n 1 -p \"Press any key to continue...\"\n        fi\n    done\n\n    print_color $GREEN \"✅ Final blockchain selection: ${selected_blockchains[*]}\"\n\n    CONFIG_DIR=$OTNODE_DIR/..\n    perform_step touch $CONFIG_DIR/.origintrail_noderc \"Configuring node config file\"\n    perform_step $(jq --null-input '{\"logLevel\": \"trace\", \"auth\": {\"ipWhitelist\": [\"::1\", \"127.0.0.1\"]}, \"modules\": {\"blockchain\": {\"implementation\": {}}}}' > $CONFIG_DIR/.origintrail_noderc) \"Adding initial config to node config file\"\n\n    perform_step $(jq --arg tripleStore \"$tripleStore\" --arg tripleStoreUrl \"$tripleStoreUrl\" '.modules.tripleStore.implementation[$tripleStore] |=\n        {\n            \"enabled\": \"true\",\n            \"config\": {\n                \"repositories\": {\n                    \"dkg\": {\n                  \"url\": $tripleStoreUrl,\n                  \"name\": \"dkg\",\n                  \"username\": \"admin\",\n                  \"password\": \"\"\n                },\n                \"privateCurrent\": {\n                  \"url\": $tripleStoreUrl,\n                  \"name\": \"private-current\",\n                  \"username\": \"admin\",\n                  \"password\": \"\"\n                },\n                \"publicCurrent\": {\n                  \"url\": $tripleStoreUrl,\n                  \"name\": \"public-current\",\n                  \"username\": \"admin\",\n                  \"password\": \"\"\n                }\n                }\n            }\n        } + .' $CONFIG_DIR/.origintrail_noderc > $CONFIG_DIR/origintrail_noderc_tmp) \"Adding triple store config to node config file\"\n\n    perform_step mv $CONFIG_DIR/origintrail_noderc_tmp $CONFIG_DIR/.origintrail_noderc \"Finalizing initial node config file\"\n\n    # Function to configure a blockchain\n    configure_blockchain() {\n        local blockchain=$1\n        local blockchain_id=$2\n\n        request_operational_wallet_keys $blockchain\n        local EVM_OP_WALLET_KEYS=$OP_WALLET_KEYS_JSON\n\n        read -p \"Enter your EVM management wallet address for $blockchain: \" EVM_MANAGEMENT_WALLET\n        text_color $GREEN \"EVM management wallet address for $blockchain: $EVM_MANAGEMENT_WALLET\"\n\n        read -p \"$(print_color $YELLOW \"Enter your profile node name : \")\" NODE_NAME\n        print_color $GREEN \"✅ Profile node name : $NODE_NAME\"\n\n        validate_operator_fees $blockchain\n\n        local RPC_ENDPOINT=\"\"\n        if [ \"$blockchain\" == \"gnosis\" ] || [ \"$blockchain\" == \"base\" ]; then\n            read -p \"Enter your $blockchain RPC endpoint: \" RPC_ENDPOINT\n            text_color $GREEN \"$blockchain RPC endpoint: $RPC_ENDPOINT\"\n            \n            # Store RPC endpoint in a global associative array for later use\n            declare -g \"${blockchain}_rpc_endpoint=$RPC_ENDPOINT\"\n        fi\n\n        local jq_filter=$(cat <<EOF\n        .modules.blockchain.implementation[\"$blockchain:$blockchain_id\"] = {\n            \"enabled\": true,\n            \"config\": {\n                \"operationalWallets\": $EVM_OP_WALLET_KEYS,\n                \"evmManagementWalletPublicKey\": \"$EVM_MANAGEMENT_WALLET\",\n                \"nodeName\": \"$NODE_NAME\",\n                \"operatorFee\": $OPERATOR_FEE\n            }\n        }\nEOF\n        )\n\n        if [ -n \"$RPC_ENDPOINT\" ]; then\n            jq_filter+=\" | .modules.blockchain.implementation[\\\"$blockchain:$blockchain_id\\\"].config.rpcEndpoints = [\\\"$RPC_ENDPOINT\\\"]\"\n        fi\n\n        jq \"$jq_filter\" $CONFIG_DIR/.origintrail_noderc > $CONFIG_DIR/origintrail_noderc_tmp\n        mv $CONFIG_DIR/origintrail_noderc_tmp $CONFIG_DIR/.origintrail_noderc\n    }\n\n    # Function to configure blockchain events services\n    configure_blockchain_events_services() {\n        local blockchain=$1\n        local blockchain_id=$2\n\n        print_color $CYAN \"🔧 Configuring Blockchain Events Service for $blockchain (ID: $blockchain_id)...\"\n\n        # Get previously stored RPC endpoint instead of asking again\n        local stored_rpc_var=\"${blockchain}_rpc_endpoint\"\n        local RPC_ENDPOINT=\"${!stored_rpc_var}\"\n        \n        # If no stored RPC endpoint is found (which shouldn't happen), ask for it\n        if [ -z \"$RPC_ENDPOINT\" ]; then\n            read -p \"$(print_color $YELLOW \"Enter your RPC endpoint for $blockchain: \")\" RPC_ENDPOINT\n        else\n            print_color $GREEN \"✅ Using previously provided RPC endpoint for $blockchain\"\n        fi\n        \n        print_color $GREEN \"✅ RPC endpoint: $RPC_ENDPOINT\"\n\n        # Correct `jq` usage to safely initialize and update the configuration\n        local jq_filter='\n            .modules |= (if .blockchainEvents == null then .blockchainEvents = {implementation: {}} else . end) |\n            .modules.blockchainEvents.implementation |= (if .[\"ot-ethers\"] == null then .[\"ot-ethers\"] = {enabled: false, config: {}} else . end) |\n            .modules.blockchainEvents.implementation[\"ot-ethers\"].enabled = true |\n            .modules.blockchainEvents.implementation[\"ot-ethers\"].config |= (if .blockchains == null then .blockchains = [] else . end) |\n            .modules.blockchainEvents.implementation[\"ot-ethers\"].config |= (if .rpcEndpoints == null then .rpcEndpoints = {} else . end) |\n            .modules.blockchainEvents.implementation[\"ot-ethers\"].config.blockchains += [\"'\"$blockchain:$blockchain_id\"'\"] |\n            .modules.blockchainEvents.implementation[\"ot-ethers\"].config.rpcEndpoints[\"'\"$blockchain:$blockchain_id\"'\"] = [\"'\"$RPC_ENDPOINT\"'\"]\n        '\n\n        # Apply the configuration changes\n        if jq \"$jq_filter\" \"$CONFIG_DIR/.origintrail_noderc\" > \"$CONFIG_DIR/.origintrail_noderc_tmp\"; then\n            mv \"$CONFIG_DIR/.origintrail_noderc_tmp\" \"$CONFIG_DIR/.origintrail_noderc\"\n            chmod 600 \"$CONFIG_DIR/.origintrail_noderc\"\n            print_color $GREEN \"✅ Successfully configured Blockchain Events Service for $blockchain (ID: $blockchain_id).\"\n        else\n            print_color $RED \"❌ Failed to configure Blockchain Events Service for $blockchain (ID: $blockchain_id).\"\n            exit 1\n        fi\n    }\n\n    # Configure blockchain events service for Base Sepolia\n    for blockchain in \"${selected_blockchains[@]}\"; do\n            case \"$blockchain\" in\n                \"Neuroweb\")\n                    configure_blockchain \"otp\" $otp_blockchain_id\n                    ;;\n                \"Gnosis\")\n                    configure_blockchain \"gnosis\" $gnosis_blockchain_id\n                    ;;\n                \"Base\" | \"Base-Sepolia\")\n                    configure_blockchain \"base\" $base_blockchain_id\n                    ;;\n            esac\n    done\n\n    for blockchain in \"${selected_blockchains[@]}\"; do\n            case \"$blockchain\" in\n                \"Gnosis\")\n                    configure_blockchain_events_services \"gnosis\" $gnosis_blockchain_id\n                    ;;\n                \"Base\" | \"Base-Sepolia\")\n                    configure_blockchain_events_services \"base\" $base_blockchain_id\n                    ;;\n            esac\n    done\n    # Now execute npm install after configuring wallets\n    print_color $CYAN \"📦 Installing npm packages...\"\n    perform_step npm ci --omit=dev --ignore-scripts \"Executing npm install\"\n\n    print_color $CYAN \"🔧 Setting up system service...\"\n    perform_step cp $OTNODE_DIR/installer/data/otnode.service /lib/systemd/system/ \"Copying otnode service file\"\n\n    print_color $CYAN \"🚀 Starting OriginTrail node...\"\n    systemctl daemon-reload\n    perform_step systemctl enable otnode \"Enabling otnode\"\n    perform_step systemctl start otnode \"Starting otnode\"\n    perform_step systemctl status otnode \"Checking otnode status\"\n\n    print_color $GREEN \"✅ OriginTrail node installation complete!\"\n}\n\n\n\n#For Arch Linux installation\nif [[ ! -z $(grep \"arch\" \"/etc/os-release\") ]]; then\n    source <(curl -s https://raw.githubusercontent.com/OriginTrail/ot-node/v8/develop/installer/data/archlinux)\nfi\n\n\n\n# Perform checks\nheader_color \"Checking Ubuntu version\"\ncheck_ubuntu_version\n\nheader_color \"Checking root privilege\"\ncheck_root\n\n\n\n#### INSTALLATION START ####\nclear\n\ncd /root\n\nheader_color $BGREEN\"Welcome to the OriginTrail Installer. Please sit back while the installer runs. \"\n\nheader_color $BGREEN\"Installing OriginTrail node pre-requisites...\"\n\ninstall_prereqs\n\nheader_color $BGREEN\"Preparing OriginTrail node directory...\"\n\nif [[ -d \"$OTNODE_DIR\" ]]; then\n    read -p \"Previous ot-node directory detected. Would you like to overwrite it? (Default: Yes) [Y]es [N]o [E]xit \" choice\n    case \"$choice\" in\n        [nN]* ) text_color $GREEN\"Keeping previous ot-node directory.\";;\n        [eE]* ) text_color $RED\"Installer stopped by user\"; exit;;\n        * ) text_color $GREEN\"Reconfiguring ot-node directory.\"; systemctl is-active --quiet otnode && systemctl stop otnode; perform_step rm -rf $OTNODE_DIR \"Deleting $OTNODE_DIR\"; install_directory;;\n    esac\nelse\n    install_directory\nfi\n\nOTNODE_DIR=$OTNODE_DIR/current\n\nheader_color $BGREEN\"Installing Triplestore (Graph Database)...\"\n\necho \"\"\necho -e \"${CYAN}┌─────────────────────────────────────────────┐${RESET}\"\necho -e \"${CYAN}│     TRIPLESTORE SELECTION                   │${RESET}\"\necho -e \"${CYAN}└─────────────────────────────────────────────┘${RESET}\"\necho \"\"\necho -e \"Please select the database you would like to use for your graph data:\"\necho -e \"  [1] ${GREEN}Blazegraph${RESET} - Default choice, recommended for most users\"\necho -e \"  [2] ${GREEN}Fuseki${RESET}     - Alternative option\"\necho -e \"  [E] ${RED}Exit${RESET}       - Cancel installation\"\necho \"\"\nread -p \"▶ Your choice: \" choice\n\ncase \"$choice\" in\n    [2] ) text_color $GREEN \"✅ Fuseki selected. Proceeding with installation.\"; tripleStore=ot-fuseki; tripleStoreUrl=\"http://localhost:3030\";;\n    [Ee] )  text_color $RED \"❌ Installer stopped by user\"; exit;;\n    * )     text_color $GREEN \"✅ Blazegraph selected. Proceeding with installation.\"; tripleStore=ot-blazegraph; tripleStoreUrl=\"http://localhost:9999\";;\nesac\n\nif [[ $tripleStore = \"ot-fuseki\" ]]; then\n    if [[ -d \"$OTNODE_DIR/../fuseki\" ]]; then\n        read -p \"Previously installed Fuseki triplestore detected. Would you like to overwrite it? (Default: Yes) [Y]es [N]o [E]xit \" choice\n        case \"$choice\" in\n            [nN]* ) text_color $GREEN\"Keeping previous Fuseki installation.\";;\n            [eE]* ) text_color $RED\"Installer stopped by user\"; exit;;\n            * )     text_color $GREEN\"Reinstalling Fuseki.\"; perform_step rm -rf $OTNODE_DIR/../fuseki \"Removing previous Fuseki installation\"; install_fuseki;;\n        esac\n    else\n        install_fuseki\n    fi\nfi\n\nif [[ $tripleStore = \"ot-blazegraph\" ]]; then\n    if [[ -f \"blazegraph.jar\" ]]; then\n        read -p \"Previously installed Blazegraph triplestore detected. Would you like to overwrite it? (Default: Yes) [Y]es [N]o [E]xit \" choice\n        case \"$choice\" in\n            [nN]* ) text_color $GREEN\"Keeping old Blazegraph Installation.\";;\n            [eE]* ) text_color $RED\"Installer stopped by user\"; exit;;\n            * )     text_color $GREEN\"Reinstalling Blazegraph.\"; perform_step rm -rf blazegraph* \"Removing previous Blazegraph installation\"; install_blazegraph;;\n        esac\n    else\n        install_blazegraph\n    fi\nfi\n\n\n\nheader_color $BGREEN\"Installing SQL...\"\n\ninstall_sql\n\nheader_color $BGREEN\"Configuring OriginTrail node...\"\n\ninstall_node\n\nheader_color $BGREEN\"INSTALLATION COMPLETE!\"\n\n# Create a more visually appealing summary\necho -e \"${GREEN}╔═══════════════════════════════════════════════════════════╗${RESET}\"\necho -e \"${GREEN}║                                                           ║${RESET}\"\necho -e \"${GREEN}║      🎉  OriginTrail Node Successfully Installed!  🎉     ║${RESET}\"\necho -e \"${GREEN}║                                                           ║${RESET}\"\necho -e \"${GREEN}╚═══════════════════════════════════════════════════════════╝${RESET}\"\necho \"\"\necho -e \"${CYAN}📊 Node Information:${RESET}\"\necho -e \" • Environment: ${YELLOW}$SELECTED_NODE_ENV${RESET}\"\necho -e \" • Triple Store: ${YELLOW}$tripleStore${RESET}\"\necho -e \" • SQL Database: ${YELLOW}$sql${RESET}\"\necho \"\"\necho -e \"${CYAN}📋 Node Management Commands:${RESET}\"\necho -e \" • ${YELLOW}otnode-restart${RESET} - Restart the node service\"\necho -e \" • ${YELLOW}otnode-stop${RESET}    - Stop the node service\"\necho -e \" • ${YELLOW}otnode-start${RESET}   - Start the node service\"\necho -e \" • ${YELLOW}otnode-logs${RESET}    - View real-time node logs\"\necho -e \" • ${YELLOW}otnode-config${RESET}  - Edit node configuration\"\necho \"\"\necho -e \"${CYAN}💡 To start using these commands, run:${RESET}\"\necho -e \"   ${YELLOW}source ~/.bashrc${RESET}\"\necho \"\"\necho -e \"${CYAN}📜 Logs will be displayed below. Press ${BOLD}Ctrl+C${RESET}${CYAN} to exit the logs.${RESET}\"\necho -e \"${CYAN}   The node will continue running in the background.${RESET}\"\necho \"\"\necho -e \"${YELLOW}⚠️  If logs do not appear or the screen freezes, press Ctrl+C to exit${RESET}\"\necho -e \"${YELLOW}   and then reboot your server.${RESET}\"\necho \"\"\n\nread -p \"▶ Press Enter to view logs...\" \n\nsystemctl restart systemd-journald\njournalctl -u otnode --output cat -fn 200\n\ntext_color $GREEN \"\nNew aliases added:\notnode-restart\notnode-stop\notnode-start\notnode-logs\notnode-config\n\nTo start using aliases, run:\nsource ~/.bashrc\n\"\ntext_color $YELLOW\"Logs will be displayed. Press ctrl+c to exit the logs. The node WILL stay running after you return to the command prompt.\n\nIf the logs do not show and the screen hangs, press ctrl+c to exit the installation and reboot your server.\n\n\"\nread -p \"Press enter to continue...\"\n"
  },
  {
    "path": "ot-node.js",
    "content": "import DeepExtend from 'deep-extend';\nimport rc from 'rc';\nimport EventEmitter from 'events';\nimport { createRequire } from 'module';\nimport { execSync } from 'child_process';\nimport DependencyInjection from './src/service/dependency-injection.js';\nimport Logger from './src/logger/logger.js';\nimport { MIN_NODE_VERSION, PARANET_ACCESS_POLICY } from './src/constants/constants.js';\nimport FileService from './src/service/file-service.js';\nimport OtnodeUpdateCommand from './src/commands/common/otnode-update-command.js';\nimport OtAutoUpdater from './src/modules/auto-updater/implementation/ot-auto-updater.js';\nimport MigrationExecutor from './src/migration/migration-executor.js';\n\nconst require = createRequire(import.meta.url);\nconst { setTimeout } = require('timers/promises');\nconst pjson = require('./package.json');\nconst configjson = require('./config/config.json');\n\nclass OTNode {\n    constructor(config) {\n        this.initializeConfiguration(config);\n        this.initializeLogger();\n        this.initializeFileService();\n        this.initializeAutoUpdaterModule();\n        this.checkNodeVersion();\n\n        // Set up process event listeners\n        process.on('SIGINT', () => this.handleExit()); // Ctrl+C\n        process.on('SIGTERM', () => this.handleExit()); // kill command or Docker stop\n    }\n\n    async start() {\n        await this.checkForUpdate();\n        await this.removeUpdateFile();\n\n        await MigrationExecutor.executeTripleStoreUserConfigurationMigration(\n            this.container,\n            this.logger,\n            this.config,\n        );\n\n        await MigrationExecutor.executeRedisSetupMigration(\n            this.container,\n            this.logger,\n            this.config,\n        );\n\n        this.logger.info('██████╗ ██╗  ██╗ ██████╗     ██╗   ██╗ █████╗ ');\n        this.logger.info('██╔══██╗██║ ██╔╝██╔════╝     ██║   ██║██╔══██╗');\n        this.logger.info('██║  ██║█████╔╝ ██║  ███╗    ██║   ██║╚█████╔╝');\n        this.logger.info('██║  ██║██╔═██╗ ██║   ██║    ╚██╗ ██╔╝██╔══██╗');\n        this.logger.info('██████╔╝██║  ██╗╚██████╔╝     ╚████╔╝ ╚█████╔╝');\n        this.logger.info('╚═════╝ ╚═╝  ╚═╝ ╚═════╝       ╚═══╝   ╚════╝ ');\n\n        this.logger.info('======================================================');\n        this.logger.info(`             OriginTrail Node v${pjson.version}`);\n        this.logger.info('======================================================');\n        this.logger.info(`Node is running in ${process.env.NODE_ENV} environment`);\n\n        await this.initializeDependencyContainer();\n        this.initializeEventEmitter();\n        await this.initializeModules();\n        this.initializeBlockchainEventsService();\n        await this.initializeShardingTableService();\n        await this.initializeParanets();\n\n        await this.createProfiles();\n\n        await this.initializeCommandExecutor();\n\n        await this.initializeRouters();\n        await this.startNetworkModule();\n        this.resumeCommandExecutor();\n        await this.initializeProofing();\n        await this.initializeClaimRewards();\n        await this.initializeSyncService();\n\n        this.logger.info('Node is up and running!');\n    }\n\n    checkNodeVersion() {\n        const nodeMajorVersion = process.versions.node.split('.')[0];\n        this.logger.warn('======================================================');\n        this.logger.warn(`Using node.js version: ${process.versions.node}`);\n        if (nodeMajorVersion < MIN_NODE_VERSION) {\n            this.logger.warn(\n                `This node was tested with node.js version 16. To make sure that your node is running properly please update your node version!`,\n            );\n        }\n        this.logger.warn('======================================================');\n    }\n\n    initializeLogger() {\n        this.logger = new Logger(this.config.logging.defaultLevel);\n    }\n\n    initializeFileService() {\n        this.fileService = new FileService({ config: this.config, logger: this.logger });\n    }\n\n    initializeAutoUpdaterModule() {\n        this.autoUpdaterModuleManager = new OtAutoUpdater();\n        this.autoUpdaterModuleManager.initialize(\n            this.config.modules.autoUpdater.implementation['ot-auto-updater'].config,\n            this.logger,\n        );\n    }\n\n    initializeConfiguration(userConfig) {\n        const defaultConfig = JSON.parse(JSON.stringify(configjson[process.env.NODE_ENV]));\n\n        if (userConfig) {\n            this.config = DeepExtend(defaultConfig, userConfig);\n        } else {\n            this.config = rc(pjson.name, defaultConfig);\n        }\n        if (!this.config.configFilename) {\n            // set default user configuration filename\n            this.config.configFilename = '.origintrail_noderc';\n        }\n    }\n\n    async initializeDependencyContainer() {\n        this.container = await DependencyInjection.initialize();\n        DependencyInjection.registerValue(this.container, 'config', this.config);\n        DependencyInjection.registerValue(this.container, 'logger', this.logger);\n\n        this.logger.info('Dependency injection module is initialized');\n    }\n\n    async initializeModules() {\n        const initializationPromises = [];\n        for (const moduleName in this.config.modules) {\n            const moduleManagerName = `${moduleName}ModuleManager`;\n\n            const moduleManager = this.container.resolve(moduleManagerName);\n            initializationPromises.push(moduleManager.initialize());\n        }\n        try {\n            await Promise.all(initializationPromises);\n            this.logger.info(`All modules initialized!`);\n        } catch (e) {\n            this.logger.error(`Module initialization failed. Error message: ${e.message}`);\n            this.stop(1);\n        }\n    }\n\n    initializeEventEmitter() {\n        const eventEmitter = new EventEmitter();\n        DependencyInjection.registerValue(this.container, 'eventEmitter', eventEmitter);\n\n        this.logger.info('Event emitter initialized');\n    }\n\n    async initializeRouters() {\n        try {\n            this.logger.info('Initializing http api and rpc router');\n\n            const routerNames = ['httpApiRouter', 'rpcRouter'];\n            await Promise.all(\n                routerNames.map(async (routerName) => {\n                    const router = this.container.resolve(routerName);\n                    try {\n                        await router.initialize();\n                    } catch (error) {\n                        this.logger.error(\n                            `${routerName} initialization failed. Error message: ${error.message}, ${error.stackTrace}`,\n                        );\n                        this.stop(1);\n                    }\n                }),\n            );\n            this.logger.info('Routers initialized successfully');\n        } catch (error) {\n            this.logger.error(\n                `Failed to initialize routers: ${error.message}, ${error.stackTrace}`,\n            );\n            this.stop(1);\n        }\n    }\n\n    async createProfiles() {\n        const cryptoService = this.container.resolve('cryptoService');\n        const blockchainModuleManager = this.container.resolve('blockchainModuleManager');\n        const networkModuleManager = this.container.resolve('networkModuleManager');\n        const peerId = networkModuleManager.getPeerId().toB58String();\n        const createProfilesPromises = blockchainModuleManager\n            .getImplementationNames()\n            .map(async (blockchain) => {\n                try {\n                    const identityExists = await blockchainModuleManager.identityIdExists(\n                        blockchain,\n                    );\n                    if (!identityExists) {\n                        this.logger.info(`Creating profile on network: ${blockchain}`);\n                        await blockchainModuleManager.createProfile(blockchain, peerId);\n\n                        if (\n                            process.env.NODE_ENV === 'development' ||\n                            process.env.NODE_ENV === 'test'\n                        ) {\n                            const blockchainConfig =\n                                blockchainModuleManager.getModuleConfiguration(blockchain);\n                            execSync(\n                                `npm run set-ask -- --rpcEndpoint=${\n                                    blockchainConfig.rpcEndpoints[0]\n                                } --ask=${1 + Math.random() * 0.5} --privateKey=${\n                                    blockchainConfig.operationalWallets[0].privateKey\n                                } --hubContractAddress=${blockchainConfig.hubContractAddress}`,\n                                { stdio: 'inherit' },\n                            );\n                            await setTimeout(10000 + Math.random() * 10000);\n                            execSync(\n                                `npm run set-stake -- --rpcEndpoint=${blockchainConfig.rpcEndpoints[0]} --stake=${blockchainConfig.initialStakeAmount} --operationalWalletPrivateKey=${blockchainConfig.operationalWallets[0].privateKey} --managementWalletPrivateKey=${blockchainConfig.evmManagementWalletPrivateKey} --hubContractAddress=${blockchainConfig.hubContractAddress}`,\n                                { stdio: 'inherit' },\n                            );\n                        }\n                    }\n\n                    const identityId = await blockchainModuleManager.getIdentityId(blockchain);\n\n                    this.logger.info(`Identity ID: ${identityId}`);\n\n                    if (identityExists) {\n                        const onChainNodeId = await blockchainModuleManager.getNodeId(\n                            blockchain,\n                            identityId,\n                        );\n                        const onChainPeerId = cryptoService.convertHexToAscii(onChainNodeId);\n\n                        if (peerId !== onChainPeerId) {\n                            this.logger.warn(\n                                `Local peer id: ${peerId} doesn't match on chain peer id: ${onChainPeerId} for blockchain: ${blockchain}, identity id: ${identityId}.`,\n                            );\n                            blockchainModuleManager.removeImplementation(blockchain);\n                        }\n                    }\n                } catch (error) {\n                    this.logger.warn(\n                        `Unable to create ${blockchain} blockchain profile. Removing implementation. Error: ${error.message}`,\n                    );\n                    blockchainModuleManager.removeImplementation(blockchain);\n                }\n            });\n\n        await Promise.all(createProfilesPromises);\n\n        if (!blockchainModuleManager.getImplementationNames().length) {\n            this.logger.error(`Unable to create blockchain profiles. OT-node shutting down...`);\n            this.stop(1);\n        }\n    }\n\n    async initializeCommandExecutor() {\n        try {\n            const commandExecutor = this.container.resolve('commandExecutor');\n            await commandExecutor.pauseCommandExecutor();\n            await commandExecutor.addDefaultCommands();\n            // commandExecutor\n            //     .replayOldCommands()\n            //     .then(() => this.logger.info('Finished replaying old commands'));\n        } catch (e) {\n            this.logger.error(\n                `Command executor initialization failed. Error message: ${e.message}`,\n            );\n            this.stop(1);\n        }\n    }\n\n    resumeCommandExecutor() {\n        try {\n            const commandExecutor = this.container.resolve('commandExecutor');\n            commandExecutor.resumeCommandExecutor();\n        } catch (e) {\n            this.logger.error(\n                `Unable to resume command executor queue. Error message: ${e.message}`,\n            );\n\n            this.stop(1);\n        }\n    }\n\n    async startNetworkModule() {\n        const networkModuleManager = this.container.resolve('networkModuleManager');\n        await networkModuleManager.start();\n    }\n\n    async initializeShardingTableService() {\n        try {\n            const shardingTableService = this.container.resolve('shardingTableService');\n            await shardingTableService.initialize();\n            this.logger.info('Sharding Table Service initialized successfully');\n        } catch (error) {\n            this.logger.error(\n                `Unable to initialize sharding table service. Error message: ${error.message} OT-node shutting down...`,\n            );\n            this.stop(1);\n        }\n    }\n\n    initializeBlockchainEventsService() {\n        try {\n            const blockchainEventsService = this.container.resolve('blockchainEventsService');\n            blockchainEventsService.initializeBlockchainEventsServices();\n            this.logger.info('Blockchain Events Service initialized successfully');\n        } catch (error) {\n            this.logger.error(\n                `Unable to initialize Blockchain Events Service. Error message: ${error.message} OT-node shutting down...`,\n            );\n            this.stop(1);\n        }\n    }\n\n    async removeUpdateFile() {\n        const updateFilePath = this.fileService.getUpdateFilePath();\n        await this.fileService.removeFile(updateFilePath).catch((error) => {\n            this.logger.warn(`Unable to remove update file. Error: ${error}`);\n        });\n        this.config.otNodeUpdated = true;\n    }\n\n    async checkForUpdate() {\n        const autoUpdaterCommand = new OtnodeUpdateCommand({\n            logger: this.logger,\n            config: this.config,\n            fileService: this.fileService,\n            autoUpdaterModuleManager: this.autoUpdaterModuleManager,\n        });\n\n        await autoUpdaterCommand.execute();\n    }\n\n    async initializeParanets() {\n        const blockchainModuleManager = this.container.resolve('blockchainModuleManager');\n        const tripleStoreService = this.container.resolve('tripleStoreService');\n        const paranetService = this.container.resolve('paranetService');\n        const ualService = this.container.resolve('ualService');\n        const validParanets = [];\n\n        const syncParanets =\n            this.config.assetSync && this.config.assetSync.syncParanets\n                ? this.config.assetSync.syncParanets\n                : [];\n        for (const paranetUAL of syncParanets) {\n            if (!ualService.isUAL(paranetUAL)) {\n                this.logger.warn(\n                    `Unable to initialize Paranet with id ${paranetUAL} because of invalid UAL format`,\n                );\n                continue;\n            }\n\n            const { blockchain, contract, knowledgeCollectionId, knowledgeAssetId } =\n                ualService.resolveUAL(paranetUAL);\n\n            if (!knowledgeAssetId) {\n                this.logger.warn(\n                    `Invalid paranet UAL: ${paranetUAL} . Knowledge asset token id is required!`,\n                );\n                continue;\n            }\n            if (!blockchainModuleManager.getImplementationNames().includes(blockchain)) {\n                this.logger.warn(\n                    `Unable to initialize Paranet with id ${paranetUAL} because of unsupported blockchain implementation`,\n                );\n                continue;\n            }\n\n            const paranetId = paranetService.constructParanetId(\n                contract,\n                knowledgeCollectionId,\n                knowledgeAssetId,\n            );\n            // eslint-disable-next-line no-await-in-loop\n            const paranetExists = await blockchainModuleManager.paranetExists(\n                blockchain,\n                paranetId,\n            );\n            if (!paranetExists) {\n                this.logger.warn(\n                    `Unable to initialize Paranet with id ${paranetUAL} because it doesn't exist`,\n                );\n                continue;\n            }\n\n            // eslint-disable-next-line no-await-in-loop\n            const nodesAccessPolicy = await blockchainModuleManager.getNodesAccessPolicy(\n                blockchain,\n                paranetId,\n            );\n            if (nodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n                // eslint-disable-next-line no-await-in-loop\n                const identityId = await blockchainModuleManager.getIdentityId(blockchain);\n                // eslint-disable-next-line no-await-in-loop\n                const isPermissionedNode = await blockchainModuleManager.isPermissionedNode(\n                    blockchain,\n                    paranetId,\n                    identityId,\n                );\n                if (!isPermissionedNode) {\n                    this.logger.warn(\n                        `Unable to initialize Paranet with id ${paranetUAL} because node with id ${identityId} is not a permissioned node`,\n                    );\n                    continue;\n                }\n            }\n\n            validParanets.push(paranetUAL);\n\n            // eslint-disable-next-line no-await-in-loop\n            await paranetService.initializeParanetRecord(blockchain, paranetId);\n        }\n        this.config.assetSync.syncParanets = validParanets;\n        tripleStoreService.initializeRepositories();\n    }\n\n    async initializeProofing() {\n        const proofingService = this.container.resolve('proofingService');\n        await proofingService.initialize();\n    }\n\n    async initializeClaimRewards() {\n        const claimRewardsService = this.container.resolve('claimRewardsService');\n        await claimRewardsService.initialize();\n    }\n\n    async initializeSyncService() {\n        const syncService = this.container.resolve('syncService');\n        await syncService.initialize();\n    }\n\n    stop(code = 0) {\n        this.logger.info('Stopping node...');\n        process.exit(code);\n    }\n\n    async handleExit() {\n        this.logger.info('SIGINT or SIGTERM received. Shutting down...');\n        const commandExecutor = this.container.resolve('commandExecutor');\n        await commandExecutor.commandExecutorShutdown();\n        process.exit(0);\n    }\n}\n\nexport default OTNode;\n"
  },
  {
    "path": "package.json",
    "content": "{\n    \"name\": \"origintrail_node\",\n    \"version\": \"8.2.6\",\n    \"description\": \"OTNode V8\",\n    \"main\": \"index.js\",\n    \"type\": \"module\",\n    \"scripts\": {\n        \"compile-contracts\": \"npm explore dkg-evm-module -- npm run compile\",\n        \"bootstrap-node\": \"node index.js tools/local-network-setup/.bootstrap_origintrail_noderc\",\n        \"start\": \"node index.js\",\n        \"prepare\": \"husky install\",\n        \"lint-staged\": \"lint-staged\",\n        \"create-account-mapping-signature\": \"node tools/ot-parachain-account-mapping/create-account-mapping-signature.js \",\n        \"start:local_blockchain\": \"npm explore dkg-evm-module -- npm run dev -- --port\",\n        \"start:local_blockchain:v1\": \"npm explore dkg-evm-module -- npm run dev:v1 -- --port\",\n        \"start:local_blockchain:v2\": \"npm explore dkg-evm-module -- npm run dev:v2 -- --port\",\n        \"kill:local_blockchain\": \"npx kill-port --port\",\n        \"test:bdd\": \"bash test/bdd/run-bdd.sh\",\n        \"test:unit\": \"nyc --all mocha --exit $(find test/unit -name '*.js')\",\n        \"test:modules\": \"nyc --all mocha --exit $(find test/unit/modules -name '*.js')\",\n        \"test:bdd:release\": \"cucumber-js --tags=@release --fail-fast --format progress --format-options '{\\\"colorsEnabled\\\": true}' test/bdd/ --import test/bdd/steps/\",\n        \"test:bdd:publish-errors\": \"cucumber-js --tags=@publish-errors --fail-fast --format progress --format-options '{\\\"colorsEnabled\\\": true}' test/bdd/ --import test/bdd/steps/\",\n        \"test:bdd:update-errors\": \"cucumber-js --tags=@update-errors --fail-fast --format progress --format-options '{\\\"colorsEnabled\\\": true}' test/bdd/ --import test/bdd/steps/\",\n        \"test:bdd:get-errors\": \"cucumber-js --tags=@get-errors --fail-fast --format progress --format-options '{\\\"colorsEnabled\\\": true}' test/bdd/ --import test/bdd/steps/\",\n        \"lint\": \"eslint .\",\n        \"set-ask\": \"node scripts/set-ask.js\",\n        \"set-stake\": \"node scripts/set-stake.js\",\n        \"set-operator-fee\": \"node scripts/set-operator-fee.js\"\n    },\n    \"repository\": {\n        \"type\": \"git\",\n        \"url\": \"git+https://github.com/OriginTrail/ot-node.git\"\n    },\n    \"keywords\": [\n        \"ot-node\",\n        \"v8\"\n    ],\n    \"author\": \"TraceLabs\",\n    \"license\": \"ISC\",\n    \"bugs\": {\n        \"url\": \"https://github.com/OriginTrail/ot-node/issues\"\n    },\n    \"homepage\": \"https://origintrail.io/\",\n    \"engines\": {\n        \"node\": \">=16.0.0\",\n        \"npm\": \">=8.0.0\"\n    },\n    \"devDependencies\": {\n        \"@cucumber/cucumber\": \"^11.2.0\",\n        \"chai\": \"^4.3.6\",\n        \"concurrently\": \"^9.1.2\",\n        \"d3\": \"^7.8.5\",\n        \"d3-node\": \"^3.0.0\",\n        \"dkg.js\": \"^8.0.8\",\n        \"eslint\": \"^8.23.0\",\n        \"eslint-config-airbnb\": \"^19.0.4\",\n        \"eslint-config-prettier\": \"^8.5.0\",\n        \"hardhat\": \"^2.22.19\",\n        \"husky\": \"^8.0.1\",\n        \"lint-staged\": \"^13.0.3\",\n        \"mocha\": \"^10.0.0\",\n        \"nyc\": \"^15.1.0\",\n        \"prettier\": \"^2.7.1\",\n        \"rollup\": \"^4.40.0\",\n        \"sharp\": \"^0.32.6\",\n        \"sinon\": \"^14.0.0\",\n        \"slugify\": \"^1.6.5\"\n    },\n    \"dependencies\": {\n        \"@comunica/query-sparql\": \"^4.0.2\",\n        \"@ethersproject/bytes\": \"^5.7.0\",\n        \"@ethersproject/hash\": \"^5.7.0\",\n        \"@ethersproject/wallet\": \"^5.7.0\",\n        \"@polkadot/api\": \"^9.3.2\",\n        \"@polkadot/keyring\": \"^10.1.7\",\n        \"@polkadot/util\": \"^10.1.7\",\n        \"@polkadot/util-crypto\": \"^10.1.7\",\n        \"@questdb/nodejs-client\": \"^3.0.0\",\n        \"app-root-path\": \"^3.1.0\",\n        \"assertion-tools\": \"^8.0.6\",\n        \"async\": \"^3.2.4\",\n        \"async-mutex\": \"^0.3.2\",\n        \"awilix\": \"^7.0.3\",\n        \"axios\": \"^1.6.0\",\n        \"bullmq\": \"^5.56.1\",\n        \"cors\": \"^2.8.5\",\n        \"deep-extend\": \"^0.6.0\",\n        \"dkg-evm-module\": \"git+https://github.com/OriginTrail/dkg-evm-module.git#main\",\n        \"dotenv\": \"^16.0.1\",\n        \"ethers\": \"^5.7.2\",\n        \"express\": \"^4.18.1\",\n        \"express-fileupload\": \"^1.4.0\",\n        \"express-rate-limit\": \"^6.5.2\",\n        \"fs-extra\": \"^10.1.0\",\n        \"graphdb\": \"^2.0.2\",\n        \"ip\": \"^1.1.8\",\n        \"it-length-prefixed\": \"^5.0.3\",\n        \"it-map\": \"^1.0.6\",\n        \"it-pipe\": \"^1.1.0\",\n        \"jsonld\": \"^8.1.0\",\n        \"jsonschema\": \"^1.4.1\",\n        \"jsonwebtoken\": \"^9.0.0\",\n        \"libp2p\": \"^0.32.4\",\n        \"libp2p-bootstrap\": \"^0.13.0\",\n        \"libp2p-kad-dht\": \"^0.24.2\",\n        \"libp2p-mplex\": \"^0.10.7\",\n        \"libp2p-noise\": \"^4.0.0\",\n        \"libp2p-tcp\": \"^0.17.2\",\n        \"mcl-wasm\": \"^1.7.0\",\n        \"minimist\": \"^1.2.7\",\n        \"ms\": \"^2.1.3\",\n        \"mysql2\": \"^3.3.0\",\n        \"peer-id\": \"^0.15.3\",\n        \"pino\": \"^9.7.0\",\n        \"pino-pretty\": \"^13.0.0\",\n        \"rc\": \"^1.2.8\",\n        \"rolling-rate-limiter\": \"^0.2.13\",\n        \"semver\": \"^7.5.2\",\n        \"sequelize\": \"^6.29.0\",\n        \"sqlite\": \"^5.1.1\",\n        \"sqlite3\": \"^5.1.7\",\n        \"timeout-abort-controller\": \"^3.0.0\",\n        \"toobusy-js\": \"^0.5.1\",\n        \"uint8arrays\": \"^3.1.0\",\n        \"umzug\": \"^3.2.1\",\n        \"unzipper\": \"^0.10.11\",\n        \"uuid\": \"^8.3.2\"\n    }\n}\n"
  },
  {
    "path": "scripts/copy-assertions.js",
    "content": "import 'dotenv/config';\nimport fs from 'fs-extra';\nimport rc from 'rc';\nimport appRootPath from 'app-root-path';\nimport path from 'path';\nimport { TRIPLE_STORE_REPOSITORIES, SCHEMA_CONTEXT } from '../src/constants/constants.js';\nimport TripleStoreModuleManager from '../src/modules/triple-store/triple-store-module-manager.js';\nimport DataService from '../src/service/data-service.js';\nimport Logger from '../src/logger/logger.js';\n\nconst { readFile } = fs;\nconst generalConfig = JSON.parse(await readFile(path.join(appRootPath.path, 'config/config.json')));\nconst pjson = JSON.parse(await readFile(path.join(appRootPath.path, 'package.json')));\n\nconst defaultConfig = generalConfig[process.env.NODE_ENV];\n\nconst config = rc(pjson.name, defaultConfig);\nconst logger = new Logger(config.loglevel);\n\nconst tripleStoreModuleManager = new TripleStoreModuleManager({ config, logger });\nawait tripleStoreModuleManager.initialize();\nconst dataService = new DataService({ config, logger });\n\nconst repositoryImplementations = {};\nfor (const implementationName of tripleStoreModuleManager.getImplementationNames()) {\n    for (const repository in tripleStoreModuleManager.getImplementation(implementationName).module\n        .repositories) {\n        repositoryImplementations[repository] = implementationName;\n    }\n}\n\nconst fromRepository = TRIPLE_STORE_REPOSITORIES.PUBLIC_CURRENT;\nconst fromImplementation = repositoryImplementations[TRIPLE_STORE_REPOSITORIES.PUBLIC_CURRENT];\nconst fromRepositoryName =\n    tripleStoreModuleManager.getImplementation(fromImplementation).module.repositories[\n        fromRepository\n    ].name;\n\nconst toRepository = TRIPLE_STORE_REPOSITORIES.PRIVATE_CURRENT;\nconst toImplementation = repositoryImplementations[TRIPLE_STORE_REPOSITORIES.PRIVATE_CURRENT];\nconst toRepositoryName =\n    tripleStoreModuleManager.getImplementation(toImplementation).module.repositories[toRepository]\n        .name;\n\nasync function getAssertions(implementation, repository) {\n    const graphs = await tripleStoreModuleManager.select(\n        implementation,\n        repository,\n        `SELECT DISTINCT ?g \n        WHERE {\n            GRAPH ?g { ?s ?p ?o }\n        }`,\n    );\n\n    return (graphs ?? []).filter(({ g }) => g.startsWith('assertion:')).map(({ g }) => g);\n}\n\nfunction logPercentage(index, max) {\n    const previousPercentage = (Math.max(0, index - 1) / max) * 100;\n    const currentPercentage = (index / max) * 100;\n\n    if (Math.floor(currentPercentage) - Math.floor(previousPercentage) < 1) return;\n\n    logger.debug(`Migration at ${Math.floor(currentPercentage * 10) / 10}%`);\n}\n\nlet toRepositoryAssertions = await getAssertions(toImplementation, toRepository);\nlogger.info(\n    `${toRepositoryAssertions.length} assertions found in ${toRepository} repository before migration`,\n);\n\nlogger.info(\n    `Starting to copy assertions from ${fromImplementation} repository ${fromRepository} with name ${fromRepositoryName} to repository ${toImplementation} repository ${toRepository} with name ${toRepositoryName}`,\n);\n\nconst fromRepositoryAssertions = await getAssertions(fromImplementation, fromRepository);\nlogger.info(`${fromRepositoryAssertions.length} assertions found in ${fromRepository}`);\n\nlet completed = 0;\nconst copyAssertion = async (g) => {\n    if (!toRepositoryAssertions.includes(g)) {\n        let nquads;\n        try {\n            nquads = await tripleStoreModuleManager.construct(\n                fromImplementation,\n                fromRepository,\n                `PREFIX schema: <${SCHEMA_CONTEXT}>\n                    CONSTRUCT { ?s ?p ?o }\n                    WHERE {\n                        {\n                            GRAPH <${g}>\n                            {\n                                ?s ?p ?o .\n                            }\n                        }\n                    }`,\n            );\n\n            nquads = await dataService.toNQuads(nquads, 'application/n-quads');\n        } catch (error) {\n            logger.error(\n                `Error while getting assertion ${g.substring(\n                    'assertion:'.length,\n                )} from ${fromImplementation} repository ${fromRepository} with name ${fromRepositoryName}. Error: ${\n                    error.message\n                }`,\n            );\n            process.exit(1);\n        }\n\n        try {\n            await tripleStoreModuleManager.insertKnowledgeCollection(\n                toImplementation,\n                toRepository,\n                g.substring('assertion:'.length),\n                nquads.join('\\n'),\n            );\n        } catch (error) {\n            logger.error(\n                `Error while inserting assertion ${g.substring(\n                    'assertion:'.length,\n                )} with nquads: ${nquads} in ${toImplementation} repository ${toRepository} with name ${toRepositoryName}. Error: ${\n                    error.message\n                }`,\n            );\n            process.exit(1);\n        }\n    }\n\n    completed += 1;\n    logPercentage(completed, fromRepositoryAssertions.length);\n};\n\nconst start = Date.now();\nconst concurrency = 10;\nlet promises = [];\nfor (let i = 0; i < fromRepositoryAssertions.length; i += 1) {\n    promises.push(copyAssertion(fromRepositoryAssertions[i]));\n    if (promises.length > concurrency) {\n        // eslint-disable-next-line no-await-in-loop\n        await Promise.all(promises);\n        promises = [];\n    }\n}\nawait Promise.all(promises);\n\nconst end = Date.now();\n\nlogger.info(`Migration completed! Lasted ${(end - start) / 1000} seconds.`);\n\ntoRepositoryAssertions = await getAssertions(toImplementation, toRepository);\nlogger.info(\n    `${toRepositoryAssertions.length} assertions found in ${toRepository} repository after migration`,\n);\n"
  },
  {
    "path": "scripts/set-ask.js",
    "content": "/* eslint-disable no-console */\nimport { ethers } from 'ethers';\nimport axios from 'axios';\nimport { createRequire } from 'module';\nimport {\n    TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    TRANSACTION_CONFIRMATIONS,\n} from '../src/constants/constants.js';\nimport validateArguments from './utils.js';\n\nconst require = createRequire(import.meta.url);\nconst Profile = require('dkg-evm-module/abi/Profile.json');\nconst IdentityStorage = require('dkg-evm-module/abi/IdentityStorage.json');\nconst Hub = require('dkg-evm-module/abi/Hub.json');\nconst argv = require('minimist')(process.argv.slice(1), {\n    string: ['ask', 'privateKey', 'hubContractAddress', 'gasPriceOracleLink'],\n});\n\nasync function getGasPrice(gasPriceOracleLink, hubContractAddress, provider) {\n    try {\n        if (!gasPriceOracleLink) {\n            return provider.getGasPrice();\n        }\n        let gasPrice;\n        const response = await axios.get(gasPriceOracleLink);\n        if (\n            gasPriceOracleLink === 'https://api.gnosisscan.io/api?module=proxy&action=eth_gasPrice'\n        ) {\n            gasPrice = Number(response.data.result, 10);\n        } else if (\n            gasPriceOracleLink === 'https://blockscout.chiadochain.net/api/v1/gas-price-oracle'\n        ) {\n            gasPrice = Math.round(response.data.average * 1e9);\n        } else {\n            gasPrice = Math.round(response.result * 1e9);\n        }\n        this.logger.debug(`Gas price: ${gasPrice}`);\n        return gasPrice;\n    } catch (error) {\n        return undefined;\n    }\n}\n\nasync function setAsk(rpcEndpoint, ask, walletPrivateKey, hubContractAddress, gasPriceOracleLink) {\n    const provider = new ethers.providers.JsonRpcProvider(rpcEndpoint);\n    const wallet = new ethers.Wallet(walletPrivateKey, provider);\n\n    const hubContract = new ethers.Contract(hubContractAddress, Hub, provider);\n\n    const profileAddress = await hubContract.getContractAddress('Profile');\n    const profile = new ethers.Contract(profileAddress, Profile, wallet);\n\n    const identityStorageAddress = await hubContract.getContractAddress('IdentityStorage');\n    const identityStorage = new ethers.Contract(identityStorageAddress, IdentityStorage, provider);\n\n    const identityId = await identityStorage.getIdentityId(wallet.address);\n\n    const askWei = ethers.utils.parseEther(ask);\n\n    const gasPrice = await getGasPrice(gasPriceOracleLink, hubContractAddress, provider);\n\n    const tx = await profile.updateAsk(identityId, askWei, {\n        gasPrice: gasPrice ?? 8,\n        gasLimit: 500_000,\n    });\n    await provider.waitForTransaction(\n        tx.hash,\n        TRANSACTION_CONFIRMATIONS,\n        TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    );\n}\n\nconst expectedArguments = ['rpcEndpoint', 'ask', 'privateKey', 'hubContractAddress'];\n\nif (validateArguments(argv, expectedArguments)) {\n    setAsk(\n        argv.rpcEndpoint,\n        argv.ask,\n        argv.privateKey,\n        argv.hubContractAddress,\n        argv.gasPriceOracleLink,\n    )\n        .then(() => {\n            console.log('Set ask completed');\n            process.exit(0);\n        })\n        .catch((error) => {\n            console.log('Error while setting ask. Error: ', error);\n            process.exit(1);\n        });\n} else {\n    console.log('Wrong arguments sent in script.');\n    console.log(\n        'Example: npm run set-ask -- --rpcEndpoint=<rpc_enpoint> --ask=<ask> --privateKey=<ask> --hubContractAddress=<hub_contract_address>',\n    );\n}\n"
  },
  {
    "path": "scripts/set-operator-fee.js",
    "content": "/* eslint-disable no-console */\nimport { ethers } from 'ethers';\nimport { createRequire } from 'module';\nimport {\n    NODE_ENVIRONMENTS,\n    TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    TRANSACTION_CONFIRMATIONS,\n} from '../src/constants/constants.js';\nimport validateArguments from './utils.js';\n\nconst require = createRequire(import.meta.url);\nconst Staking = require('dkg-evm-module/abi/Staking.json');\nconst IdentityStorage = require('dkg-evm-module/abi/IdentityStorage.json');\nconst Hub = require('dkg-evm-module/abi/Hub.json');\nconst argv = require('minimist')(process.argv.slice(1), {\n    string: ['operatorFee', 'privateKey', 'hubContractAddress'],\n});\n\nasync function setOperatorFee(rpcEndpoint, operatorFee, walletPrivateKey, hubContractAddress) {\n    const provider = new ethers.providers.JsonRpcProvider(rpcEndpoint);\n    const wallet = new ethers.Wallet(walletPrivateKey, provider);\n\n    const hubContract = new ethers.Contract(hubContractAddress, Hub.abi, provider);\n\n    const stakingContractAddress = await hubContract.getContractAddress('Staking');\n    const stakingContract = new ethers.Contract(stakingContractAddress, Staking.abi, wallet);\n\n    const identityStorageAddress = await hubContract.getContractAddress('IdentityStorage');\n    const identityStorage = new ethers.Contract(\n        identityStorageAddress,\n        IdentityStorage.abi,\n        provider,\n    );\n\n    const identityId = await identityStorage.getIdentityId(wallet.address);\n\n    const tx = await stakingContract.setOperatorFee(identityId, operatorFee, {\n        gasPrice: process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT ? undefined : 8,\n        gasLimit: 500_000,\n    });\n    await provider.waitForTransaction(\n        tx.hash,\n        TRANSACTION_CONFIRMATIONS,\n        TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    );\n}\n\nconst expectedArguments = ['rpcEndpoint', 'operatorFee', 'privateKey', 'hubContractAddress'];\n\nif (validateArguments(argv, expectedArguments)) {\n    setOperatorFee(argv.rpcEndpoint, argv.operatorFee, argv.privateKey, argv.hubContractAddress)\n        .then(() => {\n            console.log('Set operator fee completed');\n        })\n        .catch((error) => {\n            console.log('Error while setting operator fee. Error: ', error);\n        });\n} else {\n    console.log('Wrong arguments sent in script.');\n    console.log(\n        'Example: npm run set-operator-fee -- --rpcEndpoint=<rpc_enpoint> --operatorFee=<ask> --privateKey=<ask> --hubContractAddress=<hub_contract_address>',\n    );\n}\n"
  },
  {
    "path": "scripts/set-stake.js",
    "content": "/* eslint-disable no-console */\nimport { ethers } from 'ethers';\nimport { createRequire } from 'module';\nimport axios from 'axios';\nimport {\n    NODE_ENVIRONMENTS,\n    TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    TRANSACTION_CONFIRMATIONS,\n} from '../src/constants/constants.js';\nimport validateArguments from './utils.js';\n\nconst require = createRequire(import.meta.url);\nconst Staking = require('dkg-evm-module/abi/Staking.json');\nconst IdentityStorage = require('dkg-evm-module/abi/IdentityStorage.json');\nconst ERC20Token = require('dkg-evm-module/abi/Token.json');\nconst Hub = require('dkg-evm-module/abi/Hub.json');\nconst argv = require('minimist')(process.argv.slice(1), {\n    string: [\n        'stake',\n        'operationalWalletPrivateKey',\n        'managementWalletPrivateKey',\n        'hubContractAddress',\n        'gasPriceOracleLink',\n    ],\n});\n\nconst devEnvironment =\n    process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT ||\n    process.env.NODE_ENV === NODE_ENVIRONMENTS.TEST;\n\nasync function getGasPrice(gasPriceOracleLink, hubContractAddress, provider) {\n    try {\n        if (!gasPriceOracleLink) {\n            if (\n                hubContractAddress === '0x6C861Cb69300C34DfeF674F7C00E734e840C29C0' ||\n                hubContractAddress === '0x144eDa5cbf8926327cb2cceef168A121F0E4A299' ||\n                hubContractAddress === '0xaBfcf2ad1718828E7D3ec20435b0d0b5EAfbDf2c'\n            ) {\n                return provider.getGasPrice();\n            }\n            return devEnvironment ? undefined : 8;\n        }\n        let gasPrice;\n        const response = await axios.get(gasPriceOracleLink);\n        if (\n            gasPriceOracleLink === 'https://api.gnosisscan.io/api?module=proxy&action=eth_gasPrice'\n        ) {\n            gasPrice = Number(response.data.result, 10);\n        } else if (\n            gasPriceOracleLink === 'https://blockscout.chiadochain.net/api/v1/gas-price-oracle'\n        ) {\n            gasPrice = Math.round(response.data.average * 1e9);\n        } else {\n            gasPrice = Math.round(response.result * 1e9);\n        }\n        this.logger.debug(`Gas price: ${gasPrice}`);\n        return gasPrice;\n    } catch (error) {\n        return undefined;\n    }\n}\n\nasync function setStake(\n    rpcEndpoint,\n    stake,\n    operationalWalletPrivateKey,\n    managementWalletPrivateKey,\n    hubContractAddress,\n    gasPriceOracleLink,\n) {\n    const provider = new ethers.providers.JsonRpcProvider(rpcEndpoint);\n    const operationalWallet = new ethers.Wallet(operationalWalletPrivateKey, provider);\n    const managementWallet = new ethers.Wallet(managementWalletPrivateKey, provider);\n\n    const hubContract = new ethers.Contract(hubContractAddress, Hub, provider);\n\n    const stakingContractAddress = await hubContract.getContractAddress('Staking');\n    const stakingContract = new ethers.Contract(stakingContractAddress, Staking, managementWallet);\n\n    const identityStorageAddress = await hubContract.getContractAddress('IdentityStorage');\n    const identityStorage = new ethers.Contract(identityStorageAddress, IdentityStorage, provider);\n\n    const identityId = await identityStorage.getIdentityId(operationalWallet.address);\n\n    const tokenContractAddress = await hubContract.getContractAddress('Token');\n    const tokenContract = new ethers.Contract(tokenContractAddress, ERC20Token, managementWallet);\n\n    const stakeWei = ethers.utils.parseEther(stake);\n\n    const gasPrice = await getGasPrice(gasPriceOracleLink, hubContractAddress, provider);\n\n    let tx = await tokenContract.increaseAllowance(stakingContractAddress, stakeWei, {\n        gasPrice,\n        gasLimit: 500_000,\n    });\n\n    await provider.waitForTransaction(\n        tx.hash,\n        TRANSACTION_CONFIRMATIONS,\n        TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    );\n    // TODO: Add ABI instead of hard-coded function definition\n    tx = await stakingContract['stake(uint72,uint96)'](identityId, stakeWei, {\n        gasPrice: gasPrice ? gasPrice * 100 : undefined,\n        gasLimit: 3_000_000,\n    });\n    await provider.waitForTransaction(\n        tx.hash,\n        TRANSACTION_CONFIRMATIONS,\n        TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    );\n}\n\nconst expectedArguments = [\n    'rpcEndpoint',\n    'stake',\n    'operationalWalletPrivateKey',\n    'managementWalletPrivateKey',\n    'hubContractAddress',\n];\n\nif (validateArguments(argv, expectedArguments)) {\n    setStake(\n        argv.rpcEndpoint,\n        argv.stake,\n        argv.operationalWalletPrivateKey,\n        argv.managementWalletPrivateKey,\n        argv.hubContractAddress,\n        argv.gasPriceOracleLink,\n    )\n        .then(() => {\n            console.log('Set stake completed');\n            process.exit(0);\n        })\n        .catch((error) => {\n            console.log('Error while setting stake. Error: ', error);\n            process.exit(1);\n        });\n} else {\n    console.log('Wrong arguments sent in script.');\n    console.log(\n        'Example: npm run set-stake -- --rpcEndpoint=<rpc_enpoint> --stake=<stake> --operationalWalletPrivateKey=<private_key> --managementWalletPrivateKey=<private_key> --hubContractAddress=<hub_contract_address>',\n    );\n}\n"
  },
  {
    "path": "scripts/utils.js",
    "content": "export default function validateArguments(received, expected) {\n    for (const arg of expected) {\n        if (!received[arg]) {\n            return false;\n        }\n    }\n    return true;\n}\n"
  },
  {
    "path": "src/commands/blockchain-event-listener/blockchain-event-listener-command.js",
    "content": "import Command from '../command.js';\nimport {\n    CONTRACTS,\n    MONITORED_CONTRACT_EVENTS,\n    CONTRACT_INDEPENDENT_EVENTS,\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    MONITORED_CONTRACTS,\n    MONITORED_EVENTS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass BlockchainEventListenerCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.ualService = ctx.ualService;\n        this.blockchainEventsService = ctx.blockchainEventsService;\n        this.fileService = ctx.fileService;\n        this.operationIdService = ctx.operationIdService;\n        this.commandExecutor = ctx.commandExecutor;\n\n        this.invalidatedContracts = new Set();\n\n        this.errorType = ERROR_TYPE.BLOCKCHAIN_EVENT_LISTENER_ERROR;\n    }\n\n    async execute(command) {\n        const { blockchainId } = command.data;\n\n        const repositoryTransaction = await this.repositoryModuleManager.transaction();\n\n        try {\n            await this.fetchAndHandleBlockchainEvents(blockchainId, repositoryTransaction);\n            await repositoryTransaction.commit();\n        } catch (e) {\n            this.logger.error(\n                `Failed to fetch and process blockchain events for blockchain: ${blockchainId}. Error: ${e}`,\n            );\n            await repositoryTransaction.rollback();\n\n            return Command.repeat();\n        }\n\n        await this.repositoryModuleManager.markAllBlockchainEventsAsProcessed(blockchainId);\n\n        return Command.empty();\n    }\n\n    async fetchAndHandleBlockchainEvents(blockchainId, repositoryTransaction) {\n        const currentBlock = (await this.blockchainEventsService.getBlock(blockchainId)).number - 2;\n        const lastCheckedBlockRecord = await this.repositoryModuleManager.getLastCheckedBlock(\n            blockchainId,\n            { transaction: repositoryTransaction },\n        );\n\n        const { events: newEvents, eventsMissed } =\n            await this.blockchainEventsService.getPastEvents(\n                blockchainId,\n                MONITORED_CONTRACTS,\n                MONITORED_EVENTS,\n                lastCheckedBlockRecord?.lastCheckedBlock ?? 0,\n                currentBlock,\n            );\n\n        if (eventsMissed) {\n            const missedFrom = (lastCheckedBlockRecord?.lastCheckedBlock ?? 0) + 1;\n            this.logger.warn(\n                `[EVENT LISTENER] Blockchain events missed on ${blockchainId}! ` +\n                    `Gap too large: blocks ${missedFrom}–${currentBlock} ` +\n                    `(${currentBlock - missedFrom + 1} blocks). ` +\n                    `Publish finality for assets created during this window will not complete.`,\n            );\n        }\n\n        if (newEvents.length !== 0) {\n            this.logger.trace(\n                `Storing ${newEvents.length} new events for blockchain ${blockchainId} in the database.`,\n            );\n            await this.repositoryModuleManager.insertBlockchainEvents(newEvents, {\n                transaction: repositoryTransaction,\n            });\n        }\n\n        await this.repositoryModuleManager.updateLastCheckedBlock(\n            blockchainId,\n            currentBlock,\n            Date.now(),\n            { transaction: repositoryTransaction },\n        );\n\n        const unprocessedEvents =\n            await this.repositoryModuleManager.getAllUnprocessedBlockchainEvents(\n                blockchainId,\n                MONITORED_EVENTS,\n                { transaction: repositoryTransaction },\n            );\n\n        if (unprocessedEvents.length > 0) {\n            this.logger.trace(\n                `Handling ${unprocessedEvents.length} unprocessed blockchain events.`,\n            );\n        }\n\n        this.independentEvents = [];\n        this.dependentEvents = [];\n        for (const event of unprocessedEvents) {\n            if (this.isIndependentEvent(event.contract, event.event)) {\n                this.independentEvents.push(event);\n            } else {\n                this.dependentEvents.push(event);\n            }\n        }\n\n        this.dependentEvents.sort((a, b) => {\n            if (a.blockNumber !== b.blockNumber) {\n                return a.blockNumber - b.blockNumber;\n            }\n            if (a.transactionIndex !== b.transactionIndex) {\n                return a.transactionIndex - b.transactionIndex;\n            }\n            return a.logIndex - b.logIndex;\n        });\n\n        await Promise.all([\n            this.processIndependentEvents(currentBlock, repositoryTransaction),\n            this.processDependentEvents(currentBlock, repositoryTransaction),\n        ]);\n    }\n\n    isIndependentEvent(contractName, eventName) {\n        const contractIndependentEvents = CONTRACT_INDEPENDENT_EVENTS[contractName] || [];\n        return contractIndependentEvents.includes(eventName);\n    }\n\n    async processIndependentEvents(currentBlock, repositoryTransaction) {\n        await Promise.all(\n            this.independentEvents.map((event) =>\n                this.processEvent(event, currentBlock, repositoryTransaction),\n            ),\n        );\n    }\n\n    async processDependentEvents(currentBlock, repositoryTransaction) {\n        let index = 0;\n\n        while (index < this.dependentEvents.length) {\n            const event = this.dependentEvents[index];\n\n            // Step 1: Handle invalidated contracts\n            if (this.invalidatedContracts.has(event.contractAddress)) {\n                this.logger.info(\n                    `Skipping event ${event.event} for blockchain: ${event.blockchain}, ` +\n                        `invalidated contract: ${event.contract} (${event.contractAddress})`,\n                );\n\n                this.dependentEvents.splice(index, 1); // Remove the invalidated event\n                continue; // Restart the loop with the updated array\n            }\n\n            // Step 2: Handle new dependent events\n            if (this.newDependentEvents?.length > 0) {\n                this.logger.info(\n                    `Adding ${this.newDependentEvents.length} new dependent events before processing.`,\n                );\n\n                // Merge new events into the unprocessed part of the array\n                const combinedEvents = [\n                    ...this.dependentEvents.slice(index), // Unprocessed events\n                    ...this.newDependentEvents, // New events\n                ].sort((a, b) => {\n                    if (a.blockNumber !== b.blockNumber) {\n                        return a.blockNumber - b.blockNumber;\n                    }\n                    if (a.transactionIndex !== b.transactionIndex) {\n                        return a.transactionIndex - b.transactionIndex;\n                    }\n                    return a.logIndex - b.logIndex;\n                });\n\n                // Update dependentEvents: add back processed events + sorted combined events\n                this.dependentEvents = [...this.dependentEvents.slice(0, index), ...combinedEvents];\n\n                // Reset the new events buffer\n                this.newDependentEvents = [];\n            }\n\n            // Step 3: Process the current event\n            // eslint-disable-next-line no-await-in-loop\n            await this.processEvent(event, currentBlock, repositoryTransaction);\n\n            index += 1; // Move to the next event\n        }\n\n        // Clear invalidated contracts after processing\n        this.invalidatedContracts.clear();\n    }\n\n    async processEvent(event, currentBlock, repositoryTransaction) {\n        const handlerFunctionName = `handle${event.event}Event`;\n\n        if (typeof this[handlerFunctionName] !== 'function') {\n            this.logger.warn(`No handler for event type: ${event.event}`);\n            return;\n        }\n\n        this.logger.trace(`Processing event ${event.event} in block ${event.blockNumber}.`);\n        try {\n            await this[handlerFunctionName](event, currentBlock, repositoryTransaction);\n        } catch (error) {\n            this.logger.error(\n                `Error processing event ${event.event} in block ${event.blockNumber}: ${error.message}`,\n            );\n        }\n    }\n\n    async handleParameterChangedEvent(event) {\n        const { blockchain, contract, data } = event;\n        const { parameterName, parameterValue } = JSON.parse(data);\n        switch (contract) {\n            case CONTRACTS.PARAMETERS_STORAGE:\n                this.blockchainModuleManager.setContractCallCache(\n                    blockchain,\n                    CONTRACTS.PARAMETERS_STORAGE,\n                    parameterName,\n                    parameterValue,\n                );\n                break;\n            default:\n                this.logger.warn(\n                    `Unable to handle parameter changed event. Unknown contract name ${event.contract}`,\n                );\n        }\n    }\n\n    async handleNewContractEvent(event, currentBlock, repositoryTransaction) {\n        const { contractName, newContractAddress } = JSON.parse(event.data);\n\n        const blockchchainModuleContractAddress = this.blockchainModuleManager.getContractAddress(\n            event.blockchain,\n            contractName,\n        );\n\n        if (newContractAddress !== blockchchainModuleContractAddress) {\n            this.blockchainModuleManager.initializeContract(\n                event.blockchain,\n                contractName,\n                newContractAddress,\n            );\n        }\n\n        const blockchainEventsServiceContractAddress =\n            this.blockchainEventsService.getContractAddress(event.blockchain, contractName);\n\n        if (\n            blockchainEventsServiceContractAddress &&\n            newContractAddress !== blockchainEventsServiceContractAddress\n        ) {\n            this.blockchainEventsService.updateContractAddress(\n                event.blockchain,\n                contractName,\n                newContractAddress,\n            );\n\n            this.invalidatedContracts.add(blockchainEventsServiceContractAddress);\n\n            await this.repositoryModuleManager.removeContractEventsAfterBlock(\n                event.blockchain,\n                contractName,\n                event.contractAddress,\n                event.blockNumber,\n                event.transactionIndex,\n                { transaction: repositoryTransaction },\n            );\n\n            const { events: newEvents } = await this.blockchainEventsService.getPastEvents(\n                event.blockchain,\n                [contractName],\n                MONITORED_CONTRACT_EVENTS[contractName],\n                event.blockNumber,\n                currentBlock,\n            );\n\n            if (newEvents.length !== 0) {\n                this.logger.trace(\n                    `Storing ${newEvents.length} new events for blockchain ${event.blockchain} in the database.`,\n                );\n                await this.repositoryModuleManager.insertBlockchainEvents(newEvents, {\n                    transaction: repositoryTransaction,\n                });\n\n                this.newDependentEvents = newEvents;\n            }\n        }\n    }\n\n    async handleContractChangedEvent(event, currentBlock, repositoryTransaction) {\n        const { contractName, newContractAddress } = JSON.parse(event.data);\n\n        const blockchchainModuleContractAddress = this.blockchainModuleManager.getContractAddress(\n            event.blockchain,\n            contractName,\n        );\n\n        if (newContractAddress !== blockchchainModuleContractAddress) {\n            this.blockchainModuleManager.initializeContract(\n                event.blockchain,\n                contractName,\n                newContractAddress,\n            );\n        }\n\n        const blockchainEventsServiceContractAddress =\n            this.blockchainEventsService.getContractAddress(event.blockchain, contractName);\n\n        if (\n            blockchainEventsServiceContractAddress &&\n            newContractAddress !== blockchainEventsServiceContractAddress\n        ) {\n            this.blockchainEventsService.updateContractAddress(\n                event.blockchain,\n                contractName,\n                newContractAddress,\n            );\n\n            this.invalidatedContracts.add(blockchainEventsServiceContractAddress);\n\n            await this.repositoryModuleManager.removeContractEventsAfterBlock(\n                event.blockchain,\n                contractName,\n                event.contractAddress,\n                event.blockNumber,\n                event.transactionIndex,\n                { transaction: repositoryTransaction },\n            );\n\n            const { events: newEvents } = await this.blockchainEventsService.getPastEvents(\n                event.blockchain,\n                [contractName],\n                MONITORED_CONTRACT_EVENTS[contractName],\n                event.blockNumber,\n                currentBlock,\n            );\n\n            if (newEvents.length !== 0) {\n                this.logger.trace(\n                    `Storing ${newEvents.length} new events for blockchain ${event.blockchain} in the database.`,\n                );\n                await this.repositoryModuleManager.insertBlockchainEvents(newEvents, {\n                    transaction: repositoryTransaction,\n                });\n\n                this.newDependentEvents = newEvents;\n            }\n        }\n    }\n\n    async handleNewAssetStorageEvent(event, currentBlock, repositoryTransaction) {\n        const { contractName, newContractAddress } = JSON.parse(event.data);\n\n        const blockchchainModuleContractAddress = this.blockchainModuleManager.getContractAddress(\n            event.blockchain,\n            contractName,\n        );\n\n        if (newContractAddress !== blockchchainModuleContractAddress) {\n            this.blockchainModuleManager.initializeAssetStorageContract(\n                event.blockchain,\n                newContractAddress,\n            );\n        }\n\n        const blockchainEventsServiceContractAddress =\n            this.blockchainEventsService.getContractAddress(event.blockchain, contractName);\n\n        if (\n            blockchainEventsServiceContractAddress &&\n            newContractAddress !== blockchainEventsServiceContractAddress\n        ) {\n            this.blockchainEventsService.updateContractAddress(\n                event.blockchain,\n                contractName,\n                newContractAddress,\n            );\n\n            this.invalidatedContracts.add(blockchainEventsServiceContractAddress);\n\n            await this.repositoryModuleManager.removeContractEventsAfterBlock(\n                event.blockchain,\n                contractName,\n                event.contractAddress,\n                event.blockNumber,\n                event.transactionIndex,\n                { transaction: repositoryTransaction },\n            );\n\n            const { events: newEvents } = await this.blockchainEventsService.getPastEvents(\n                event.blockchain,\n                [contractName],\n                MONITORED_CONTRACT_EVENTS[contractName],\n                event.blockNumber,\n                currentBlock,\n            );\n\n            if (newEvents.length !== 0) {\n                this.logger.trace(\n                    `Storing ${newEvents.length} new events for blockchain ${event.blockchain} in the database.`,\n                );\n                await this.repositoryModuleManager.insertBlockchainEvents(newEvents, {\n                    transaction: repositoryTransaction,\n                });\n\n                this.newDependentEvents = newEvents;\n            }\n        }\n    }\n\n    async handleAssetStorageChangedEvent(event, currentBlock, repositoryTransaction) {\n        const { contractName, newContractAddress } = JSON.parse(event.data);\n\n        const blockchchainModuleContractAddress = this.blockchainModuleManager.getContractAddress(\n            event.blockchain,\n            contractName,\n        );\n\n        if (newContractAddress !== blockchchainModuleContractAddress) {\n            this.blockchainModuleManager.initializeAssetStorageContract(\n                event.blockchain,\n                newContractAddress,\n            );\n        }\n\n        const blockchainEventsServiceContractAddress =\n            this.blockchainEventsService.getContractAddress(event.blockchain, contractName);\n\n        if (\n            blockchainEventsServiceContractAddress &&\n            newContractAddress !== blockchainEventsServiceContractAddress\n        ) {\n            this.blockchainEventsService.updateContractAddress(\n                event.blockchain,\n                contractName,\n                newContractAddress,\n            );\n\n            this.invalidatedContracts.add(blockchainEventsServiceContractAddress);\n\n            await this.repositoryModuleManager.removeContractEventsAfterBlock(\n                event.blockchain,\n                contractName,\n                event.contractAddress,\n                event.blockNumber,\n                event.transactionIndex,\n                { transaction: repositoryTransaction },\n            );\n\n            const { events: newEvents } = await this.blockchainEventsService.getPastEvents(\n                event.blockchain,\n                [contractName],\n                MONITORED_CONTRACT_EVENTS[contractName],\n                event.blockNumber,\n                currentBlock,\n            );\n\n            if (newEvents.length !== 0) {\n                this.logger.trace(\n                    `Storing ${newEvents.length} new events for blockchain ${event.blockchain} in the database.`,\n                );\n                await this.repositoryModuleManager.insertBlockchainEvents(newEvents, {\n                    transaction: repositoryTransaction,\n                });\n\n                this.newDependentEvents = newEvents;\n            }\n        }\n    }\n\n    async handleKnowledgeCollectionCreatedEvent(event) {\n        await this.commandExecutor.add({\n            name: 'publishFinalizationCommand',\n            sequence: [],\n            data: {\n                event,\n            },\n            priority: COMMAND_PRIORITY.HIGHEST,\n            transactional: false,\n        });\n    }\n\n    // TODO: Adjust after new contracts are released\n    async handleAssetUpdatedEvent(event) {\n        const eventData = JSON.parse(event.data);\n\n        // TODO: Add correct name for assetStateIndex from event currently it's placeholder\n        const { assetContract, tokenId, state, updateOperationId, assetStateIndex } = eventData;\n        const { blockchain } = event;\n\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.UPDATE_FINALIZATION.UPDATE_FINALIZATION_START,\n            blockchain,\n        );\n\n        let datasetPath;\n        let cachedData;\n        try {\n            datasetPath = this.fileService.getPendingStorageDocumentPath(updateOperationId);\n            cachedData = await this.fileService.readFile(datasetPath, true);\n        } catch (error) {\n            this.operationIdService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                `Unable to read cached data from ${datasetPath}, error: ${error.message}`,\n                ERROR_TYPE.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_NO_CACHED_DATA,\n            );\n        }\n        const ual = this.ualService.deriveUAL(blockchain, assetContract, tokenId);\n\n        await this.commandExecutor.add({\n            name: 'updateValidateAssertionMetadataCommand',\n            sequence: ['updateAssertionCommand'],\n            delay: 0,\n            data: {\n                operationId,\n                ual,\n                blockchain,\n                contract: assetContract,\n                tokenId,\n                assetStateIndex,\n                merkleRoot: state,\n                assertion: cachedData.assertion,\n                cachedMerkleRoot: cachedData.merkleRoot,\n            },\n            transactional: false,\n        });\n    }\n\n    /**\n     * Recover system from failure\n     * @param error\n     */\n    async recover() {\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default BlockchainEventListenerCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'blockchainEventListenerCommand',\n            data: {},\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default BlockchainEventListenerCommand;\n"
  },
  {
    "path": "src/commands/blockchain-event-listener/event-listener-command.js",
    "content": "import Command from '../command.js';\nimport {\n    CONTRACT_EVENT_FETCH_INTERVALS,\n    NODE_ENVIRONMENTS,\n    ERROR_TYPE,\n    COMMAND_PRIORITY,\n    MAXIMUM_FETCH_EVENTS_FAILED_COUNT,\n} from '../../constants/constants.js';\n\nclass EventListenerCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n\n        this.errorType = ERROR_TYPE.EVENT_LISTENER_ERROR;\n    }\n\n    calculateCommandPeriod() {\n        const isDevEnvironment = [NODE_ENVIRONMENTS.DEVELOPMENT, NODE_ENVIRONMENTS.TEST].includes(\n            process.env.NODE_ENV,\n        );\n\n        return isDevEnvironment\n            ? CONTRACT_EVENT_FETCH_INTERVALS.DEVELOPMENT\n            : CONTRACT_EVENT_FETCH_INTERVALS.MAINNET;\n    }\n\n    async execute() {\n        this.logger.info('Event Listener: Starting event listener command.');\n\n        await Promise.all(\n            this.blockchainModuleManager.getImplementationNames().map(async (blockchainId) => {\n                const commandData = { blockchainId };\n\n                return this.commandExecutor.add({\n                    name: `blockchainEventListenerCommand`,\n                    data: commandData,\n                    retries: MAXIMUM_FETCH_EVENTS_FAILED_COUNT,\n                    priority: COMMAND_PRIORITY.HIGHEST,\n                    isBlocking: true,\n                    transactional: false,\n                });\n            }),\n        );\n\n        if (!this.blockchainModuleManager.getImplementationNames().length) {\n            this.logger.error(`No blockchain implementations. OT-node shutting down...`);\n            process.exit(1);\n        }\n\n        return Command.repeat();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to execute ${command.name}. Error: ${command.message}`);\n\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default eventListenerCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'eventListenerCommand',\n            delay: 0,\n            data: {},\n            transactional: false,\n            period: this.calculateCommandPeriod(),\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default EventListenerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/ask-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    ASK_CLEANUP_TIME_DELAY,\n    ASK_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass AskCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationRecords(\n            OPERATIONS.ASK,\n            nowTimestamp - ASK_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'askCleanerCommand',\n            data: {},\n            period: ASK_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default AskCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/ask-response-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    ASK_RESPONSE_CLEANUP_TIME_DELAY,\n    ASK_RESPONSE_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass AskResponseCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationResponse(\n            OPERATIONS.ASK,\n            nowTimestamp - ASK_RESPONSE_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'askResponseCleanerCommand',\n            data: {},\n            period: ASK_RESPONSE_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default AskResponseCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/batch-get-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    GET_CLEANUP_TIME_DELAY,\n    GET_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass BatchGetCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationRecords(\n            OPERATIONS.BATCH_GET,\n            nowTimestamp - GET_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'batchGetCleanerCommand',\n            data: {},\n            period: GET_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default BatchGetCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/blockchain-event-cleaner-command.js",
    "content": "import {\n    PROCESSED_BLOCKCHAIN_EVENTS_CLEANUP_TIME_MILLS,\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    PROCESSED_BLOCKCHAIN_EVENTS_CLEANUP_TIME_DELAY,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\nimport CleanerCommand from './cleaner-command.js';\n\nclass BlockchainEventCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedEvents(\n            nowTimestamp - PROCESSED_BLOCKCHAIN_EVENTS_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'blockchainEventCleanerCommand',\n            data: {},\n            period: PROCESSED_BLOCKCHAIN_EVENTS_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default BlockchainEventCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/cleaner-command.js",
    "content": "import { REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER } from '../../constants/constants.js';\nimport Command from '../command.js';\n\nclass CleanerCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute() {\n        let deletedRowsCount;\n\n        do {\n            const nowTimestamp = Date.now();\n            // eslint-disable-next-line no-await-in-loop\n            deletedRowsCount = await this.deleteRows(nowTimestamp);\n        } while (deletedRowsCount === REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER);\n\n        return Command.repeat();\n    }\n\n    // eslint-disable-next-line no-unused-vars\n    async deleteRows(nowTimestamp) {\n        throw Error('deleteRows not implemented');\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to clean operational db data: error: ${command.message}`);\n        return Command.repeat();\n    }\n}\n\nexport default CleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/commands-cleaner-command.js",
    "content": "import {\n    FINALIZED_COMMAND_CLEANUP_TIME_MILLS,\n    FINALIZED_COMMAND_CLEANUP_TIME_DELAY,\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n} from '../../constants/constants.js';\nimport CleanerCommand from './cleaner-command.js';\n\nclass CommandsCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveFinalizedCommands(\n            nowTimestamp - FINALIZED_COMMAND_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'commandsCleanerCommand',\n            data: {},\n            period: FINALIZED_COMMAND_CLEANUP_TIME_MILLS,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default CommandsCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/finality-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    FINALITY_CLEANUP_TIME_DELAY,\n    FINALITY_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass FinalityCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationRecords(\n            OPERATIONS.FINALITY,\n            nowTimestamp - FINALITY_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'finalityCleanerCommand',\n            data: {},\n            period: FINALITY_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FinalityCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/finality-response-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    FINALITY_RESPONSE_CLEANUP_TIME_DELAY,\n    FINALITY_RESPONSE_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass FinalityResponseCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationResponse(\n            OPERATIONS.FINALITY,\n            nowTimestamp - FINALITY_RESPONSE_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'finalityResponseCleanerCommand',\n            data: {},\n            period: FINALITY_RESPONSE_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FinalityResponseCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/get-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    GET_CLEANUP_TIME_DELAY,\n    GET_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass GetCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationRecords(\n            OPERATIONS.GET,\n            nowTimestamp - GET_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'getCleanerCommand',\n            data: {},\n            period: GET_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default GetCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/get-response-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    GET_RESPONSE_CLEANUP_TIME_DELAY,\n    GET_RESPONSE_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass GetResponseCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationResponse(\n            OPERATIONS.GET,\n            nowTimestamp - GET_RESPONSE_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'getResponseCleanerCommand',\n            data: {},\n            period: GET_RESPONSE_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default GetResponseCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/operation-id-cleaner-command.js",
    "content": "import Command from '../command.js';\nimport {\n    BYTES_IN_KILOBYTE,\n    OPERATION_ID_FILES_FOR_REMOVAL_MAX_NUMBER,\n    OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n    OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS,\n    OPERATION_ID_STATUS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\n/**\n * Increases approval for Bidding contract on blockchain\n */\nclass OperationIdCleanerCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.logger = ctx.logger;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.fileService = ctx.fileService;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute() {\n        let memoryBytes = 0;\n        let fileBytes = 0;\n        try {\n            memoryBytes = this.operationIdService.getOperationIdMemoryCacheSizeBytes();\n        } catch (error) {\n            this.logger.warn(`Unable to read memory cache footprint: ${error.message}`);\n        }\n        try {\n            fileBytes = await this.operationIdService.getOperationIdFileCacheSizeBytes();\n        } catch (error) {\n            this.logger.warn(`Unable to read file cache footprint: ${error.message}`);\n        }\n        const bytesInMegabyte = 1024 * 1024;\n        this.logger.debug(\n            `Operation cache footprint before cleanup: memory=${(\n                memoryBytes / bytesInMegabyte\n            ).toFixed(2)}MB, files=${(fileBytes / bytesInMegabyte).toFixed(2)}MB`,\n        );\n\n        this.logger.debug('Starting command for removal of expired cache files');\n        const timeToBeDeleted = Date.now() - OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS;\n        await this.repositoryModuleManager.removeOperationIdRecord(timeToBeDeleted, [\n            OPERATION_ID_STATUS.COMPLETED,\n            OPERATION_ID_STATUS.FAILED,\n        ]);\n        let removed = await this.operationIdService.removeExpiredOperationIdMemoryCache(\n            OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS,\n        );\n        if (removed) {\n            this.logger.debug(\n                `Successfully removed ${\n                    removed / BYTES_IN_KILOBYTE\n                } Kbs expired cached operation entries from memory`,\n            );\n        }\n        removed = await this.operationIdService.removeExpiredOperationIdFileCache(\n            OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n            OPERATION_ID_FILES_FOR_REMOVAL_MAX_NUMBER,\n        );\n        if (removed) {\n            this.logger.debug(`Successfully removed ${removed} expired cached operation files`);\n        }\n\n        return Command.repeat();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to clean operation ids table: error: ${command.message}`);\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'operationIdCleanerCommand',\n            period: OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS,\n            data: {},\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default OperationIdCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/pending-storage-cleaner-command.js",
    "content": "import Command from '../command.js';\nimport {\n    PUBLISH_STORAGE_MEMORY_CLEANUP_COMMAND_CLEANUP_TIME_MILLS,\n    PUBLISH_STORAGE_FILE_CLEANUP_COMMAND_CLEANUP_TIME_MILLS,\n    PENDING_STORAGE_FILES_FOR_REMOVAL_MAX_NUMBER,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\n/**\n * Cleans memory cache in the pending storage service\n */\nclass PendingStorageCleanerCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.logger = ctx.logger;\n        this.pendingStorageService = ctx.pendingStorageService;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute() {\n        this.logger.debug('Starting command for removal of expired pending storage entries');\n\n        const removed = await this.pendingStorageService.removeExpiredFileCache(\n            PUBLISH_STORAGE_FILE_CLEANUP_COMMAND_CLEANUP_TIME_MILLS,\n            PENDING_STORAGE_FILES_FOR_REMOVAL_MAX_NUMBER,\n        );\n        if (removed) {\n            this.logger.debug(`Successfully removed ${removed} expired cached operation files`);\n        }\n\n        return Command.repeat();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to clean pending storage: error: ${command.message}`);\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'pendingStorageCleanerCommand',\n            period: PUBLISH_STORAGE_MEMORY_CLEANUP_COMMAND_CLEANUP_TIME_MILLS,\n            data: {},\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PendingStorageCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/publish-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    PUBLISH_CLEANUP_TIME_DELAY,\n    PUBLISH_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass PublishCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationRecords(\n            OPERATIONS.PUBLISH,\n            nowTimestamp - PUBLISH_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'publishCleanerCommand',\n            data: {},\n            period: PUBLISH_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PublishCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/publish-response-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    PUBLISH_RESPONSE_CLEANUP_TIME_DELAY,\n    PUBLISH_RESPONSE_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass PublishResponseCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationResponse(\n            OPERATIONS.PUBLISH,\n            nowTimestamp - PUBLISH_RESPONSE_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'publishResponseCleanerCommand',\n            data: {},\n            period: PUBLISH_RESPONSE_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PublishResponseCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/update-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    UPDATE_CLEANUP_TIME_DELAY,\n    UPDATE_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass UpdateCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationRecords(\n            OPERATIONS.UPDATE,\n            nowTimestamp - UPDATE_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateCleanerCommand',\n            data: {},\n            period: UPDATE_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateCleanerCommand;\n"
  },
  {
    "path": "src/commands/cleaners/update-response-cleaner-command.js",
    "content": "import CleanerCommand from './cleaner-command.js';\nimport {\n    REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n    OPERATIONS,\n    UPDATE_RESPONSE_CLEANUP_TIME_DELAY,\n    UPDATE_RESPONSE_CLEANUP_TIME_MILLS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass UpdateResponseCleanerCommand extends CleanerCommand {\n    async deleteRows(nowTimestamp) {\n        return this.repositoryModuleManager.findAndRemoveProcessedOperationResponse(\n            OPERATIONS.UPDATE,\n            nowTimestamp - UPDATE_RESPONSE_CLEANUP_TIME_DELAY,\n            REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER,\n        );\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateResponseCleanerCommand',\n            data: {},\n            period: UPDATE_RESPONSE_CLEANUP_TIME_MILLS,\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOWEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateResponseCleanerCommand;\n"
  },
  {
    "path": "src/commands/command-executor.js",
    "content": "import { Queue, Worker } from 'bullmq';\nimport {\n    PERMANENT_COMMANDS,\n    DEFAULT_COMMAND_DELAY_IN_MILLS,\n    GENERAL_COMMAND_QUEUE_PARALLELISM,\n    BATCH_GET_COMMAND_QUEUE_PARALLELISM,\n    DEFAULT_COMMAND_PRIORITY,\n    MAX_COMMAND_LIFETIME,\n} from '../constants/constants.js';\n\n/**\n * Queues and processes commands\n */\nclass CommandExecutor {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.commandResolver = ctx.commandResolver;\n        this.operationIdService = ctx.operationIdService;\n\n        this.verboseLoggingEnabled = ctx.config.commandExecutorVerboseLoggingEnabled;\n        const env = process.env.NODE_ENV;\n        const queueName =\n            env === 'development'\n                ? `command-executor-${ctx.config.modules.blockchain.implementation['hardhat1:31337'].config.nodeName}`\n                : 'command-executor';\n\n        const batchGetQueueName =\n            env === 'development'\n                ? `batchGetQueue-${ctx.config.modules.blockchain.implementation['hardhat1:31337'].config.nodeName}`\n                : 'batchGetQueue';\n\n        this.queue = new Queue(queueName, {\n            connection: {\n                host: 'localhost',\n                port: 6379,\n            },\n        });\n\n        this.queueBatchGet = new Queue(batchGetQueueName, {\n            connection: {\n                host: 'localhost',\n                port: 6379,\n            },\n        });\n\n        this.batchGetWorker = new Worker(\n            batchGetQueueName,\n            async (job) => {\n                const commandData = job.data;\n\n                const createdTime = new Date(job.timestamp).getTime();\n                const now = Date.now();\n\n                if (now - createdTime > MAX_COMMAND_LIFETIME) {\n                    throw new Error('Command is too old');\n                }\n\n                this.logger.trace(`Command  started ${job.name}, ${job.id}`);\n\n                const commandName = job.name;\n\n                const handler = this.commandResolver.resolve(commandName);\n                if (!handler) {\n                    throw new Error(`Command will not be executed ${job.name}, missing handler`);\n                }\n\n                await handler.execute({ data: commandData });\n            },\n            {\n                connection: {\n                    host: 'localhost',\n                    port: 6379,\n                },\n                maxStalledCount: 0,\n                lockDuration: 3 * 60 * 1000,\n                stalledInterval: 3 * 60 * 1000,\n                concurrency: BATCH_GET_COMMAND_QUEUE_PARALLELISM,\n            },\n        );\n\n        this.worker = new Worker(\n            queueName,\n            async (job) => {\n                const commandData = job.data;\n                const createdTime = new Date(job.timestamp).getTime();\n                const now = Date.now();\n\n                if (now - createdTime > MAX_COMMAND_LIFETIME) {\n                    throw new Error('Command is too old');\n                }\n\n                this.logger.trace(`Command  started ${job.name}, ${job.id}`);\n                let commandName = job.name;\n                if (job.name.startsWith('paranetSyncCommand')) {\n                    commandName = `paranetSyncCommand`;\n                }\n\n                const handler = this.commandResolver.resolve(commandName);\n                if (!handler) {\n                    throw new Error(`Command will not be executed ${job.name}, missing handler`);\n                }\n\n                await handler.execute({ data: commandData });\n            },\n            {\n                connection: {\n                    host: 'localhost',\n                    port: 6379,\n                },\n                maxStalledCount: 0,\n                lockDuration: 3 * 60 * 1000,\n                stalledInterval: 3 * 60 * 1000,\n                concurrency: GENERAL_COMMAND_QUEUE_PARALLELISM,\n            },\n        );\n\n        this.worker.on('completed', async (job) => {\n            this.logger.trace(\n                `Job with ID ${job.id}, ${job.name} has been completed. Duration: ${\n                    job.finishedOn - job.timestamp\n                }`,\n            );\n        });\n\n        this.batchGetWorker.on('completed', async (job) => {\n            this.logger.trace(\n                `BatchGetJob with ID ${job.id}, ${job.name} has been completed. Duration: ${\n                    job.finishedOn - job.timestamp\n                }`,\n            );\n        });\n\n        this.worker.on('failed', (job, err) => {\n            this.logger.error(\n                `Job with ID ${job.id}, ${job.name} has failed with error: ${err.message}, ${err.stack}`,\n            );\n        });\n\n        this.batchGetWorker.on('failed', (job, err) => {\n            this.logger.error(\n                `BatchGetJob with ID ${job.id}, ${job.name} has failed with error: ${err.message}, ${err.stack}`,\n            );\n        });\n\n        this.queue.on('error', (err) => {\n            this.logger.error(`Queue error: ${err.message}, ${err.stack}`);\n        });\n\n        this.queueBatchGet.on('error', (err) => {\n            this.logger.error(`BatchGetQueue error: ${err.message}, ${err.stack}`);\n        });\n\n        this.worker.on('error', (err) => {\n            this.logger.error(`Worker error: ${err.message}, ${err.stack}`);\n        });\n\n        this.batchGetWorker.on('error', (err) => {\n            this.logger.error(`BatchGetWorker error: ${err.message}, ${err.stack}`);\n        });\n\n        this.queueBatchGet.on('closed', () => {\n            this.logger.trace('BatchGetQueue has been closed.');\n        });\n\n        this.queue.on('closed', () => {\n            this.logger.trace('Queue has been closed.');\n        });\n\n        setInterval(async () => {\n            const generalQueueCount = await this.queue.count();\n            const batchGetQueueCount = await this.queueBatchGet.count();\n            this.logger.trace(\n                `General queue count: ${generalQueueCount}, Batch get queue count: ${batchGetQueueCount}`,\n            );\n\n            this.operationIdService.emitChangeEvent(\n                'COMMAND_EXECUTOR_QUEUE_COUNT',\n                `command-executor-queue-count-${Date.now()}`,\n                null,\n                generalQueueCount,\n                batchGetQueueCount,\n            );\n        }, 5 * 60 * 1000);\n    }\n\n    /**\n     * Initialize executor\n     * @returns {Promise<void>}\n     */\n    async addDefaultCommands() {\n        await Promise.all(PERMANENT_COMMANDS.map((command) => this._addDefaultCommand(command)));\n\n        this.logger.trace('Command executor has been initialized...');\n    }\n\n    /**\n     * Resumes the command executor\n     */\n    async resumeCommandExecutor() {\n        if (this.verboseLoggingEnabled) {\n            this.logger.trace('Command executor has been resumed...');\n        }\n        await this.queue.resume();\n        await this.queueBatchGet.resume();\n        this.worker.resume();\n        this.batchGetWorker.resume();\n    }\n\n    /**\n     * Pause the command executor queue\n     */\n    async pauseCommandExecutor() {\n        this.logger.trace('Command executor queue has been paused...');\n        await this.queue.pause();\n        await this.worker.pause();\n        await this.queueBatchGet.pause();\n        await this.batchGetWorker.pause();\n    }\n\n    /**\n     * Starts the default command by name\n     * @param name - Command name\n     * @return {Promise<void>}\n     * @private\n     */\n    async _addDefaultCommand(name) {\n        const handler = this.commandResolver.resolve(name);\n\n        if (!handler) {\n            // Add command name to the log\n            this.logger.warn(`Command will not be executed.`);\n            return;\n        }\n\n        await this.removePeriodicCommand(['paranetSyncCommand']);\n\n        if (['eventListenerCommand', 'shardingTableCheckCommand'].includes(name)) {\n            await this.add(handler.default(), 0);\n        } else {\n            await this.add(handler.default(), DEFAULT_COMMAND_DELAY_IN_MILLS);\n        }\n\n        if (this.verboseLoggingEnabled) {\n            handler.logger.trace(`Permanent command created.`);\n        }\n    }\n\n    // TODO: Add function that removes periodic command\n    async removePeriodicCommand(commandNames) {\n        const periodicCommands = await this.queue.getJobSchedulers();\n        // Find if command with this prefix exist in repeatable commands\n        const periodicCommandsToRemove = periodicCommands.filter((command) =>\n            commandNames.some((name) => command.name.startsWith(name)),\n        );\n        await Promise.all(\n            periodicCommandsToRemove.map((command) => this.queue.removeJobScheduler(command.name)),\n        );\n    }\n\n    /**\n     * Adds single command to queue\n     * @param command\n     * @param delay\n     * @param insert\n     */\n    async add(addCommand, addDelay) {\n        const command = addCommand;\n\n        const delay = addDelay ?? 0;\n        const commandPriority = command.priority ?? DEFAULT_COMMAND_PRIORITY;\n        const jobOptions = { removeOnComplete: true, removeOnFail: true };\n        if (delay > 0) {\n            jobOptions.delay = delay;\n        }\n        jobOptions.priority = commandPriority;\n        if (command.period && command.period > 0) {\n            await this.queue.upsertJobScheduler(\n                command.name,\n                { every: command.period },\n                { name: command.name, data: command.data, opts: jobOptions },\n            );\n        } else if (\n            command.name.toLowerCase().endsWith('batchgetcommand') ||\n            command.name.toLowerCase().endsWith('batchgetrequestcommand')\n        ) {\n            await this.queueBatchGet.add(command.name, command.data, jobOptions);\n        } else {\n            await this.queue.add(command.name, command.data, jobOptions);\n        }\n    }\n\n    async commandExecutorShutdown() {\n        await this.worker.close();\n        await this.queue.close();\n        await this.queueBatchGet.close();\n        await this.batchGetWorker.close();\n    }\n}\n\nexport default CommandExecutor;\n"
  },
  {
    "path": "src/commands/command-resolver.js",
    "content": "/**\n * Resolves command handlers based on command names\n */\nclass CommandResolver {\n    constructor(ctx) {\n        this.ctx = ctx;\n        this.logger = ctx.logger;\n    }\n\n    /**\n     * Gets command handler based on command name\n     * @param name\n     * @return {*}\n     */\n    resolve(name) {\n        try {\n            return this.ctx[`${name}`];\n        } catch (e) {\n            this.logger.warn(`No handler defined for command '${name}'`);\n        }\n    }\n}\n\nexport default CommandResolver;\n"
  },
  {
    "path": "src/commands/command.js",
    "content": "import { OPERATION_ID_STATUS } from '../constants/constants.js';\n\n/**\n * Describes one command handler\n */\nclass Command {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n        this.commandResolver = ctx.commandResolver;\n        this.operationIdService = ctx.operationIdService;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     */\n    async execute() {\n        return Command.empty();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param err\n     */\n    async recover(command) {\n        const { operationId, blockchain } = command.data;\n        await this.handleError(operationId, blockchain, command.message, this.errorType, true);\n\n        return Command.empty();\n    }\n\n    /**\n     * Execute strategy when event is too late\n     */\n    async expired() {\n        return Command.empty();\n    }\n\n    /**\n     * Pack data for DB\n     * @param data\n     */\n    pack(data) {\n        return data;\n    }\n\n    /**\n     * Unpack data from DB\n     * @param data\n     */\n    unpack(data) {\n        return data;\n    }\n\n    /**\n     * Makes command from sequence and continues it\n     * @param data  -  Command data\n     * @param [sequence] - Optional command sequence\n     * @param [opts] - Optional command options\n     */\n    continueSequence(data, sequence, opts) {\n        if (!sequence || sequence.length === 0) {\n            return Command.empty();\n        }\n        const [name] = sequence;\n        const newSequence = sequence.slice(1);\n\n        const handler = this.commandResolver.resolve(name);\n        const command = handler.default();\n\n        const commandData = command.data ? command.data : {};\n        Object.assign(command, {\n            data: Object.assign(commandData, data),\n            sequence: newSequence,\n        });\n        if (opts) {\n            Object.assign(command, opts);\n        }\n        return {\n            commands: [command],\n        };\n    }\n\n    /**\n     * Builds command\n     * @param name  - Command name\n     * @param data  - Command data\n     * @param [sequence] - Optional command sequence\n     * @param [opts] - Optional command options\n     * @returns {*}\n     */\n    build(name, data, sequence, opts) {\n        const command = this.commandResolver.resolve(name).default();\n        const commandData = command.data ? command.data : {};\n        Object.assign(command, {\n            data: Object.assign(commandData, data),\n            sequence,\n        });\n        if (opts) {\n            Object.assign(command, opts);\n        }\n        return command;\n    }\n\n    async retryFinished(command) {\n        this.logger.trace(`Max retry count for command: ${command.name} reached!`);\n    }\n\n    /**\n     * Error handler for command\n     * @param operationId  - Operation operation id\n     * @param error  - Error object\n     * @param errorName - Name of error\n     * @param markFailed - Update operation status to failed\n     * @returns {*}\n     */\n    async handleError(operationId, blockchain, errorMessage, errorName, markFailed) {\n        this.logger.error(`Command error (${errorName}): ${errorMessage}`);\n        if (markFailed) {\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                blockchain,\n                OPERATION_ID_STATUS.FAILED,\n                errorMessage,\n                errorName,\n            );\n        }\n    }\n\n    /**\n     * Builds default command\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default() {\n        return {};\n    }\n\n    /**\n     * Halt execution\n     * @returns {{repeat: boolean, commands: Array}}\n     */\n    static empty() {\n        return {\n            commands: [],\n        };\n    }\n\n    /**\n     * Returns repeat info\n     * @returns {{repeat: boolean, commands: Array}}\n     */\n    static repeat() {\n        return {\n            repeat: true,\n        };\n    }\n\n    /**\n     * Returns retry info\n     * @returns {{retry: boolean, commands: Array}}\n     */\n    static retry() {\n        return {\n            retry: true,\n        };\n    }\n}\n\nexport default Command;\n"
  },
  {
    "path": "src/commands/common/dial-peers-command.js",
    "content": "import Command from '../command.js';\nimport {\n    DIAL_PEERS_COMMAND_FREQUENCY_MILLS,\n    DIAL_PEERS_CONCURRENCY,\n    MIN_DIAL_FREQUENCY_MILLIS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass DialPeersCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.shardingTableService = ctx.shardingTableService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute() {\n        const peersToDial = await this.repositoryModuleManager.getPeersToDial(\n            DIAL_PEERS_CONCURRENCY,\n            MIN_DIAL_FREQUENCY_MILLIS,\n        );\n\n        if (peersToDial.length) {\n            this.logger.trace(`Dialing ${peersToDial.length} remote peers`);\n            await Promise.all(\n                peersToDial.map(({ peerId }) => this.shardingTableService.dial(peerId)),\n            );\n        }\n\n        return Command.repeat();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to dial peers: error: ${command.message}`);\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'dialPeersCommand',\n            data: {},\n            period: DIAL_PEERS_COMMAND_FREQUENCY_MILLS,\n            priority: COMMAND_PRIORITY.MEDIUM,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default DialPeersCommand;\n"
  },
  {
    "path": "src/commands/common/log-public-addresses-command.js",
    "content": "import ip from 'ip';\nimport Command from '../command.js';\nimport { NODE_ENVIRONMENTS } from '../../constants/constants.js';\n\nclass LogPublicAddressesCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.networkModuleManager = ctx.networkModuleManager;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute() {\n        if (\n            process.env.NODE_ENV !== NODE_ENVIRONMENTS.DEVELOPMENT &&\n            process.env.NODE_ENV !== NODE_ENVIRONMENTS.DEVNET\n        )\n            return Command.empty();\n\n        const publicAddressesMap = {};\n\n        await Promise.all(\n            this.blockchainModuleManager.getImplementationNames().map(async (blockchain) => {\n                const peers = await this.repositoryModuleManager.getAllPeerRecords(blockchain);\n                await Promise.all(\n                    peers.map(async (p) => {\n                        let peerInfo = await this.networkModuleManager\n                            .getPeerInfo(p.peerId)\n                            .catch(() => ({ addresses: [] }));\n                        if (!peerInfo?.addresses.length) {\n                            peerInfo = { addresses: [] };\n                        }\n\n                        publicAddressesMap[p.peerId] = peerInfo.addresses\n                            .map((addr) => addr.multiaddr)\n                            .filter((addr) => addr.isThinWaistAddress())\n                            .filter((addr) => !ip.isPrivate(addr.toString().split('/')[2]));\n                    }),\n                );\n            }),\n        );\n\n        this.logger.debug(\n            `Found public addresses for sharding table peers: ${JSON.stringify(\n                publicAddressesMap,\n                null,\n                2,\n            )}`,\n        );\n\n        return Command.repeat();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to log public addresses: error: ${command.message}`);\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default command\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'logPublicAddressesCommand',\n            data: {},\n            period: 60 * 1000,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default LogPublicAddressesCommand;\n"
  },
  {
    "path": "src/commands/common/otnode-update-command.js",
    "content": "import semver from 'semver';\nimport Command from '../command.js';\nimport { COMMAND_PRIORITY } from '../../constants/constants.js';\n\nclass OtnodeUpdateCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.logger = ctx.logger;\n        this.config = ctx.config;\n        this.autoUpdaterModuleManager = ctx.autoUpdaterModuleManager;\n        this.fileService = ctx.fileService;\n    }\n\n    /**\n     * Performs code update by fetching new code from github repo\n     * @param command\n     */\n    async execute() {\n        if (!this.config.modules.autoUpdater.enabled) {\n            return Command.empty();\n        }\n        try {\n            this.logger.info('Checking for new updates...');\n            const { upToDate, currentVersion, remoteVersion } =\n                await this.autoUpdaterModuleManager.compareVersions();\n            if (!upToDate) {\n                if (semver.lt(semver.valid(remoteVersion), semver.valid(currentVersion))) {\n                    this.logger.info(\n                        'Remote version less than current version, update will be skipped',\n                    );\n                    return Command.repeat();\n                }\n                const success = await this.autoUpdaterModuleManager.update();\n\n                if (success) {\n                    const updateFolderPath = this.fileService.getDataFolderPath();\n                    await this.fileService.writeContentsToFile(\n                        updateFolderPath,\n                        'UPDATED',\n                        'UPDATED',\n                    );\n                    this.logger.info('Node will now restart!');\n                    process.exit(1);\n                }\n                this.logger.info('Unable to update ot-node to new version.');\n            } else {\n                this.logger.info('Your node is running on the latest version!');\n            }\n        } catch (error) {\n            await this.handleError(error.message);\n        }\n        return Command.repeat();\n    }\n\n    async recover(command) {\n        await this.handleError(command.message);\n\n        return Command.repeat();\n    }\n\n    async handleError(errorMessage) {\n        this.logger.error(`Error in update command: ${errorMessage}`);\n    }\n\n    /**\n     * Builds default otnodeUpdateCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'otnodeUpdateCommand',\n            delay: 0,\n            period: 15 * 60 * 1000,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGH,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default OtnodeUpdateCommand;\n"
  },
  {
    "path": "src/commands/common/send-telemetry-command.js",
    "content": "import { createRequire } from 'module';\nimport Command from '../command.js';\nimport {\n    SEND_TELEMETRY_COMMAND_FREQUENCY_MINUTES,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nconst require = createRequire(import.meta.url);\nconst pjson = require('../../../package.json');\n\nclass SendTelemetryCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.logger = ctx.logger;\n        this.config = ctx.config;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.tripleStoreModuleManager = ctx.tripleStoreModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.telemetryModuleManager = ctx.telemetryModuleManager;\n    }\n\n    /**\n     * Performs code update by fetching new code from github repo\n     * @param command\n     */\n    async execute() {\n        if (\n            !this.config.modules.telemetry.enabled ||\n            !this.telemetryModuleManager.getModuleConfiguration().sendToSignalingService\n        ) {\n            return Command.empty();\n        }\n\n        try {\n            const events = (await this.getUnpublishedEvents()) || [];\n            const blockchainsNodeInfo = [];\n            const blockchainImplementations = this.blockchainModuleManager.getImplementationNames();\n            for (const implementation of blockchainImplementations) {\n                const blockchainInfo = {\n                    blockchain_id: implementation,\n                    // eslint-disable-next-line no-await-in-loop\n                    identity_id: await this.blockchainModuleManager.getIdentityId(implementation),\n                    operational_wallet:\n                        this.blockchainModuleManager.getPublicKeys(implementation)[0],\n                    management_wallet:\n                        this.blockchainModuleManager.getManagementKey(implementation),\n                };\n                blockchainsNodeInfo.push(blockchainInfo);\n            }\n\n            const tripleStoreNodeInfo = [];\n            const tripleStoreImplementations =\n                this.tripleStoreModuleManager.getImplementationNames();\n            for (const implementation of tripleStoreImplementations) {\n                const tripleStoreInfo = {\n                    implementationName: implementation,\n                };\n                tripleStoreNodeInfo.push(tripleStoreInfo);\n            }\n            const nodeData = {\n                version: pjson.version,\n                identity: this.networkModuleManager.getPeerId().toB58String(),\n                hostname: this.config.hostname,\n                triple_stores: tripleStoreNodeInfo,\n                auto_update_enabled: this.config.modules.autoUpdater.enabled,\n                multiaddresses: this.networkModuleManager.getMultiaddrs(),\n                blockchains: blockchainsNodeInfo,\n            };\n            const isDataSuccessfullySent = await this.telemetryModuleManager.sendTelemetryData(\n                nodeData,\n                events,\n            );\n            if (isDataSuccessfullySent && events?.length > 0) {\n                await this.removePublishedEvents(events);\n            }\n        } catch (error) {\n            await this.handleError(error.message);\n        }\n        return Command.repeat();\n    }\n\n    async recover(command) {\n        await this.handleError(command.message);\n\n        return Command.repeat();\n    }\n\n    async handleError(errorMessage) {\n        this.logger.error(`Error in send telemetry command: ${errorMessage}`);\n    }\n\n    /**\n     * Builds default sendTelemetryCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'sendTelemetryCommand',\n            delay: 0,\n            data: {},\n            period: SEND_TELEMETRY_COMMAND_FREQUENCY_MINUTES * 60 * 1000,\n            transactional: false,\n            priority: COMMAND_PRIORITY.MEDIUM,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n\n    async getUnpublishedEvents() {\n        return this.repositoryModuleManager.getUnpublishedEvents();\n    }\n\n    async removePublishedEvents(events) {\n        const ids = events.map((event) => event.id);\n\n        await this.repositoryModuleManager.destroyEvents(ids);\n    }\n}\n\nexport default SendTelemetryCommand;\n"
  },
  {
    "path": "src/commands/common/send-transaction-command.js",
    "content": "import Command from '../command.js';\nimport { EXPECTED_TRANSACTION_ERRORS, OPERATION_ID_STATUS } from '../../constants/constants.js';\n\nclass SendTransactionCommand extends Command {\n    async sendTransactionAndHandleResult(transactionCompletePromise, data, command) {\n        const {\n            blockchain,\n            agreementId,\n            epoch,\n            operationId,\n            closestNode,\n            leftNeighborhoodEdge,\n            rightNeighborhoodEdge,\n            contract,\n            tokenId,\n            stateIndex,\n            txGasPrice,\n        } = data;\n        const sendTransactionOperationId = this.operationIdService.generateId();\n        let txSuccess;\n        let msgBase;\n        try {\n            this.operationIdService.emitChangeEvent(\n                this.txStartStatus,\n                sendTransactionOperationId,\n                blockchain,\n                agreementId,\n                epoch,\n                operationId,\n            );\n            txSuccess = await transactionCompletePromise;\n        } catch (error) {\n            this.logger.warn(\n                `Failed to execute ${command.name}, Error Message: ${error.message} for the Service Agreement ` +\n                    `with the ID: ${agreementId}, Blockchain: ${blockchain}, Contract: ${contract}, ` +\n                    `Token ID: ${tokenId},` +\n                    `Epoch: ${epoch}, State Index: ${stateIndex}, Operation ID: ${operationId}, ` +\n                    `Closest Node: ${closestNode}, Left neighborhood edge: ${leftNeighborhoodEdge}, ` +\n                    `Right neighborhood edge: ${rightNeighborhoodEdge}, ` +\n                    `Retry number: ${this.commandRetryNumber - command.retries + 1}.`,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.FAILED,\n                sendTransactionOperationId,\n                blockchain,\n                error.message,\n                this.txErrorType,\n            );\n            txSuccess = false;\n            if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.NODE_ALREADY_SUBMITTED_COMMIT)) {\n                msgBase = 'Node has already submitted commit. Finishing';\n            } else if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.NODE_ALREADY_REWARDED)) {\n                msgBase = 'Node already rewarded. Finishing';\n            } else if (\n                error.message.includes(EXPECTED_TRANSACTION_ERRORS.SERVICE_AGREEMENT_DOESNT_EXIST)\n            ) {\n                msgBase = 'Service agreement doesnt exist. Finishing';\n            } else if (\n                error.message.includes(\n                    EXPECTED_TRANSACTION_ERRORS.INVALID_PROXIMITY_SCORE_FUNCTIONS_PAIR_ID,\n                )\n            ) {\n                msgBase = 'Invalid proximity score functions pair id. Finishing';\n            } else if (\n                error.message.includes(EXPECTED_TRANSACTION_ERRORS.INVALID_SCORE_FUNCTION_ID)\n            ) {\n                msgBase = 'Invalid score function id. Finishing';\n            } else if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.COMMIT_WINDOW_CLOSED)) {\n                msgBase = 'Commit window closed. Finishing';\n            } else if (\n                error.message.includes(EXPECTED_TRANSACTION_ERRORS.NODE_NOT_IN_SHARDING_TABLE)\n            ) {\n                msgBase = 'Node not in sharding table. Finishing';\n            } else if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.PROOF_WINDOW_CLOSED)) {\n                msgBase = 'Proof window closed. Finishing';\n            } else if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.NODE_NOT_AWARDED)) {\n                msgBase = 'Node not awarded. Finishing';\n            } else if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.WRONG_MERKLE_PROOF)) {\n                msgBase = 'Wrong merkle proof. Finishing';\n            } else if (error.message.includes(EXPECTED_TRANSACTION_ERRORS.INSUFFICIENT_FUNDS)) {\n                msgBase = 'Insufficient funds. Finishing';\n                if (this.insufficientFundsErrorReceived) {\n                    await this.insufficientFundsErrorReceived(command.data);\n                }\n            } else {\n                let newGasPrice;\n                if (\n                    error.message.includes(EXPECTED_TRANSACTION_ERRORS.TIMEOUT_EXCEEDED) ||\n                    error.message.includes(EXPECTED_TRANSACTION_ERRORS.TOO_LOW_PRIORITY)\n                ) {\n                    newGasPrice = Math.ceil(txGasPrice * this.txGasIncreaseFactor);\n                } else {\n                    newGasPrice = null;\n                }\n\n                Object.assign(command, {\n                    data: { ...command.data, gasPrice: newGasPrice },\n                    message: error.message,\n                });\n\n                return Command.retry();\n            }\n        }\n\n        if (txSuccess) {\n            this.operationIdService.emitChangeEvent(\n                this.txEndStatus,\n                sendTransactionOperationId,\n                blockchain,\n                agreementId,\n                epoch,\n                operationId,\n            );\n            msgBase = 'Successfully executed';\n\n            this.operationIdService.emitChangeEvent(\n                this.operationEndStatus,\n                operationId,\n                blockchain,\n                agreementId,\n                epoch,\n            );\n        }\n\n        this.logger.trace(\n            `${msgBase} ${command.name} for the Service Agreement with the ID: ${agreementId}, ` +\n                `Blockchain: ${blockchain}, Contract: ${contract}, Token ID: ${tokenId}, ` +\n                `Epoch: ${epoch}, ` +\n                `State Index: ${stateIndex}, Operation ID: ${operationId}, ` +\n                `Closest Node: ${closestNode}, Left neighborhood edge: ${leftNeighborhoodEdge}, ` +\n                `Right neighborhood edge: ${rightNeighborhoodEdge}, ` +\n                `Retry number: ${this.commandRetryNumber - command.retries + 1}`,\n        );\n\n        return Command.empty();\n    }\n\n    /**\n     * Builds default sendTransactionCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'sendTransactionCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default SendTransactionCommand;\n"
  },
  {
    "path": "src/commands/common/sharding-table-check-command.js",
    "content": "import Command from '../command.js';\nimport {\n    COMMAND_PRIORITY,\n    SHARDING_TABLE_CHECK_COMMAND_FREQUENCY_MILLS,\n} from '../../constants/constants.js';\n\nclass ShardingTableCheckCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.logger = ctx.logger;\n        this.config = ctx.config;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.shardingTableService = ctx.shardingTableService;\n    }\n\n    /**\n     * Checks sharding table size on blockchain and compares to local\n     * If not equal, removes local and pulls new from blockchain\n     * @param command\n     */\n    async execute() {\n        const repositoryTransaction = await this.repositoryModuleManager.transaction();\n\n        try {\n            const promises = this.blockchainModuleManager\n                .getImplementationNames()\n                .map(async (blockchainId) => {\n                    this.logger.debug(\n                        `Performing sharding table check for blockchain ${blockchainId}.`,\n                    );\n                    const shardingTableLength =\n                        await this.blockchainModuleManager.getShardingTableLength(blockchainId);\n                    const totalNodesNumber = await this.repositoryModuleManager.getPeersCount(\n                        blockchainId,\n                    );\n\n                    if (shardingTableLength !== totalNodesNumber) {\n                        return this.shardingTableService.pullBlockchainShardingTable(\n                            blockchainId,\n                            repositoryTransaction,\n                        );\n                    }\n                });\n\n            await Promise.all(promises);\n            await repositoryTransaction.commit();\n        } catch (error) {\n            await repositoryTransaction.rollback();\n            await this.handleError(error.message);\n        }\n        return Command.repeat();\n    }\n\n    async recover(command) {\n        await this.handleError(command.message);\n\n        return Command.repeat();\n    }\n\n    async handleError(errorMessage) {\n        this.logger.error(`Error in sharding table check command: ${errorMessage}`);\n    }\n\n    /**\n     * Builds default shardingTableCheckCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'shardingTableCheckCommand',\n            delay: 0,\n            data: {},\n            period: SHARDING_TABLE_CHECK_COMMAND_FREQUENCY_MILLS,\n            priority: COMMAND_PRIORITY.HIGHEST,\n            isBlocking: true,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default ShardingTableCheckCommand;\n"
  },
  {
    "path": "src/commands/common/validate-asset-command.js",
    "content": "import Command from '../command.js';\nimport {\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    LOCAL_STORE_TYPES,\n    PARANET_ACCESS_POLICY,\n} from '../../constants/constants.js';\n\nclass ValidateAssetCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.ualService = ctx.ualService;\n        this.dataService = ctx.dataService;\n        this.validationService = ctx.validationService;\n        this.paranetService = ctx.paranetService;\n\n        this.errorType = ERROR_TYPE.VALIDATE_ASSET_ERROR;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const {\n            operationId,\n            blockchain,\n            contract,\n            tokenId,\n            storeType = LOCAL_STORE_TYPES.TRIPLE,\n            paranetUAL,\n        } = command.data;\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.VALIDATE_ASSET_START,\n        );\n\n        // TODO: Validate number of triplets and other stuff we did before so it matches like we did it in v6\n        const cachedData = await this.operationIdService.getCachedOperationIdData(operationId);\n        const ual = this.ualService.deriveUAL(blockchain, contract, tokenId);\n\n        // backwards compatibility\n        const cachedAssertion = cachedData.datasetRoot || cachedData.public.assertionId;\n        const cachedDataset = cachedData.dataset || cachedData.public.assertion;\n\n        // V0 backwards compatibility\n        if (cachedData.private?.assertionId && cachedData.private?.assertion) {\n            this.logger.info(\n                `Validating asset's private assertion with id: ${cachedData.private.assertionId} ual: ${ual}`,\n            );\n\n            try {\n                await this.validationService.validateDatasetRoot(\n                    cachedData.private.assertion,\n                    cachedData.private.assertionId,\n                );\n            } catch (error) {\n                await this.handleError(\n                    operationId,\n                    blockchain,\n                    error.message,\n                    this.errorType,\n                    true,\n                );\n                return Command.empty();\n            }\n        }\n\n        await this.validationService.validateDatasetRoot(cachedDataset, cachedAssertion);\n\n        let paranetId;\n        if (storeType === LOCAL_STORE_TYPES.TRIPLE_PARANET) {\n            try {\n                const {\n                    blockchain: paranetBlockchain,\n                    contract: paranetContract,\n                    knowledgeCollectionId: paranetKnowledgeCollectionId,\n                    knowledgeAssetId: paranetKnowledgeAssetId,\n                } = this.ualService.resolveUAL(paranetUAL);\n\n                if (!paranetKnowledgeAssetId) {\n                    await this.handleError(\n                        operationId,\n                        blockchain,\n                        `Invalid paranet UAL: ${paranetUAL} . Knowledge asset token id is required!`,\n                        this.errorType,\n                        true,\n                    );\n                    return Command.empty();\n                }\n\n                paranetId = this.paranetService.constructParanetId(\n                    paranetContract,\n                    paranetKnowledgeCollectionId,\n                    paranetKnowledgeAssetId,\n                );\n                const paranetExists = await this.blockchainModuleManager.paranetExists(\n                    paranetBlockchain,\n                    paranetId,\n                );\n                if (!paranetExists) {\n                    await this.handleError(\n                        operationId,\n                        blockchain,\n                        `Paranet: ${paranetId} doesn't exist.`,\n                        this.errorType,\n                        true,\n                    );\n                    return Command.empty();\n                }\n\n                const nodesAccessPolicy = await this.blockchainModuleManager.getNodesAccessPolicy(\n                    blockchain,\n                    paranetId,\n                );\n                if (nodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n                    const identityId = await this.blockchainModuleManager.getIdentityId(blockchain);\n                    const isPermissionedNode =\n                        await this.blockchainModuleManager.isPermissionedNode(\n                            blockchain,\n                            paranetId,\n                            identityId,\n                        );\n                    if (!isPermissionedNode) {\n                        await this.handleError(\n                            operationId,\n                            blockchain,\n                            `Node is not part of permissioned paranet ${paranetId}  because node with id ${identityId} is not a permissioned node.`,\n                            this.errorType,\n                            true,\n                        );\n                        return Command.empty();\n                    }\n                } else {\n                    await this.handleError(\n                        operationId,\n                        blockchain,\n                        `Paranet ${paranetId} is not permissioned paranet.`,\n                        this.errorType,\n                        true,\n                    );\n                    return Command.empty();\n                }\n            } catch (error) {\n                await this.handleError(\n                    operationId,\n                    blockchain,\n                    error.message,\n                    this.errorType,\n                    true,\n                );\n                return Command.empty();\n            }\n        }\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.VALIDATE_ASSET_END,\n        );\n        return this.continueSequence(\n            { ...command.data, paranetId, retry: undefined, period: undefined },\n            command.sequence,\n        );\n    }\n\n    async retryFinished(command) {\n        const { blockchain, contract, tokenId, operationId } = command.data;\n        const ual = this.ualService.deriveUAL(blockchain, contract, tokenId);\n        await this.handleError(\n            operationId,\n            blockchain,\n            `Max retry count for command: ${command.name} reached! Unable to validate ual: ${ual}`,\n            this.errorType,\n            true,\n        );\n    }\n\n    /**\n     * Builds default validateAssetCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'validateAssetCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default ValidateAssetCommand;\n"
  },
  {
    "path": "src/commands/paranet/paranet-sync-command.js",
    "content": "/* eslint-disable no-await-in-loop */\nimport { setTimeout } from 'timers/promises';\nimport Command from '../command.js';\nimport {\n    ERROR_TYPE,\n    PARANET_SYNC_FREQUENCY_MILLS,\n    OPERATION_ID_STATUS,\n    PARANET_SYNC_PARAMETERS,\n    PARANET_SYNC_KA_COUNT,\n    PARANET_SYNC_RETRIES_LIMIT,\n    PARANET_SYNC_RETRY_DELAY_MS,\n    OPERATION_STATUS,\n    PARANET_NODES_ACCESS_POLICIES,\n    PARANET_ACCESS_POLICY,\n    TRIPLES_VISIBILITY,\n    DKG_METADATA_PREDICATES,\n    TRIPLE_STORE_REPOSITORIES,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass ParanetSyncCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.ualService = ctx.ualService;\n        this.paranetService = ctx.paranetService;\n        this.getService = ctx.getService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n\n        this.errorType = ERROR_TYPE.PARANET.PARANET_SYNC_ERROR;\n    }\n\n    // TODO: Fix logs? Use word 'Knowledge Collection' or 'Collection' instead of 'Asset'.\n    async execute(command) {\n        const { blockchain, operationId, paranetUAL, paranetId, nodesAccessPolicy } = command.data;\n        const paranetNodesAccessPolicy = PARANET_NODES_ACCESS_POLICIES[nodesAccessPolicy];\n\n        this.logger.info(\n            `Paranet sync: Starting paranet sync for paranet: ${paranetUAL} (${paranetId}), operation ID: ${operationId}, access policy ${paranetNodesAccessPolicy}`,\n        );\n\n        const countContract = (\n            await this.blockchainModuleManager.getParanetKnowledgeCollectionCount(\n                blockchain,\n                paranetId,\n            )\n        ).toNumber();\n        const countDatabase = await this.repositoryModuleManager.getParanetKcCount(paranetUAL);\n\n        const missingUALs = (\n            await this.blockchainModuleManager.getParanetKnowledgeCollectionLocatorsWithPagination(\n                blockchain,\n                paranetId,\n                countDatabase,\n                countContract,\n            )\n        ).map(({ knowledgeCollectionStorageContract, knowledgeCollectionTokenId }) =>\n            this.ualService.deriveUAL(\n                blockchain,\n                knowledgeCollectionStorageContract,\n                knowledgeCollectionTokenId,\n            ),\n        );\n\n        await this.repositoryModuleManager.createParanetKcRecords(\n            paranetUAL,\n            blockchain,\n            missingUALs,\n        );\n\n        const countSynced = await this.repositoryModuleManager.getParanetKcSyncedCount(paranetUAL);\n        const countUnsynced = await this.repositoryModuleManager.getParanetKcUnsyncedCount(\n            paranetUAL,\n        );\n\n        this.logger.info(\n            `Paranet sync: Paranet: ${paranetUAL} (${paranetId}) Total count of Paranet KAs in the contract: ${countContract}; Synced KAs count: ${countSynced};  Total count of missed KAs: ${countUnsynced}`,\n        );\n\n        if (countUnsynced === 0) {\n            this.logger.info(\n                `Paranet sync: No new assets to sync for paranet: ${paranetUAL} (${paranetId}), operation ID: ${operationId}!`,\n            );\n            return Command.repeat();\n        }\n        // #region Sync batch;\n        const syncBatch = await this.repositoryModuleManager.getParanetKcSyncBatch(\n            paranetUAL,\n            PARANET_SYNC_RETRIES_LIMIT,\n            PARANET_SYNC_RETRY_DELAY_MS,\n            PARANET_SYNC_KA_COUNT,\n        );\n\n        this.logger.info(\n            `Paranet sync: Attempting to sync ${syncBatch.length} missed assets for paranet: ${paranetUAL} (${paranetId}), operation ID: ${operationId}!`,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.PARANET.PARANET_SYNC_MISSED_KAS_SYNC_START,\n        );\n\n        const syncResults = await Promise.all(\n            syncBatch.map(({ ual }) =>\n                this.syncKc(paranetUAL, ual, paranetId, nodesAccessPolicy, operationId),\n            ),\n        );\n\n        const countSyncSuccessful = syncResults.filter((err) => !err).length;\n        const countSyncFailed = syncResults.length - countSyncSuccessful;\n\n        this.logger.info(\n            `Paranet sync: Successful missed assets syncs: ${countSyncSuccessful}; ` +\n                `Failed missed assets syncs: ${countSyncFailed}  for paranet: ${paranetUAL} ` +\n                `(${paranetId}), operation ID: ${operationId}!`,\n        );\n        // #endregion\n\n        await this.operationIdService.updateOperationIdStatusWithValues(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.PARANET.PARANET_SYNC_MISSED_KAS_SYNC_END,\n            countSyncSuccessful,\n            countSyncFailed,\n        );\n\n        return Command.repeat();\n    }\n\n    /** **NOTE:** Throws errors! */\n    async syncKcState(\n        paranetUAL,\n        ual,\n        stateIndex,\n        assertionId,\n        paranetId,\n        paranetNodesAccessPolicy,\n    ) {\n        const { blockchain, contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n\n        const getOperationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.GET.GET_START,\n            blockchain,\n        );\n\n        // #region GET (LOCAL)\n        this.operationIdService.updateOperationIdStatus(\n            getOperationId,\n            blockchain,\n            OPERATION_ID_STATUS.GET.GET_INIT_START,\n        );\n        this.repositoryModuleManager.createOperationRecord(\n            this.getService.getOperationName(),\n            getOperationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n        this.logger.debug(\n            `Paranet sync: Get for ${ual} with operation id ${getOperationId} initiated.`,\n        );\n\n        const maxAttempts = PARANET_SYNC_PARAMETERS.GET_RESULT_POLLING_MAX_ATTEMPTS;\n        const pollingInterval = PARANET_SYNC_PARAMETERS.GET_RESULT_POLLING_INTERVAL_MILLIS;\n\n        let attempt = 0;\n        let getResult;\n\n        const contentType =\n            paranetNodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED\n                ? TRIPLES_VISIBILITY.ALL\n                : TRIPLES_VISIBILITY.PUBLIC;\n        await this.commandExecutor.add({\n            name: 'getCommand',\n            sequence: [],\n            delay: 0,\n            data: {\n                operationId: getOperationId,\n                id: ual,\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n                state: assertionId,\n                ual: this.ualService.deriveUAL(blockchain, contract, knowledgeCollectionId),\n                includeMetadata: true,\n                contentType,\n                paranetId,\n                paranetUAL,\n                paranetNodesAccessPolicy,\n                paranetSync: true,\n            },\n            transactional: false,\n        });\n\n        attempt = 0;\n        do {\n            await setTimeout(pollingInterval);\n            getResult = await this.operationIdService.getOperationIdRecord(getOperationId);\n            attempt += 1;\n        } while (\n            attempt < maxAttempts &&\n            getResult?.status !== OPERATION_ID_STATUS.FAILED &&\n            getResult?.status !== OPERATION_ID_STATUS.COMPLETED\n        );\n        // #endregion NETWORK END\n\n        if (getResult?.status !== OPERATION_ID_STATUS.COMPLETED) {\n            throw new Error(\n                `Unable to sync Knowledge Collection Id: ${knowledgeCollectionId}, for contract: ${contract}, state index: ${stateIndex}, blockchain: ${blockchain}, GET result: ${JSON.stringify(\n                    getResult,\n                )}`,\n            );\n        }\n\n        const data = await this.operationIdService.getCachedOperationIdData(getOperationId);\n        this.logger.debug(\n            `Paranet sync: ${\n                data.assertion.public.length + (data.assertion?.private?.length || 0)\n            } nquads found for asset with ual: ${ual}, state index: ${stateIndex}, assertionId: ${assertionId}`,\n        );\n\n        const metadata = {};\n        data.metadata.forEach((line) => {\n            for (const predicate of Object.values(DKG_METADATA_PREDICATES)) {\n                if (line.includes(predicate)) {\n                    switch (predicate) {\n                        case DKG_METADATA_PREDICATES.PUBLISHED_BY:\n                            metadata.publisherKey = line\n                                .split(' ')[2]\n                                .split('/')[1]\n                                .replaceAll('>', '');\n                            break;\n                        case DKG_METADATA_PREDICATES.PUBLISHED_AT_BLOCK:\n                            metadata.blockNumber = line.split(' ')[2].trim().replaceAll('\"', '');\n                            break;\n                        case DKG_METADATA_PREDICATES.PUBLISH_TX:\n                            metadata.txHash = line.split(' ')[2].trim().replaceAll('\"', '');\n                            break;\n                        case DKG_METADATA_PREDICATES.BLOCK_TIME:\n                            metadata.blockTimestamp =\n                                new Date(\n                                    line\n                                        .split(' ')[2]\n                                        .trim()\n                                        .replaceAll('\"', '')\n                                        .replaceAll(\n                                            '^^<http://www.w3.org/2001/XMLSchema#dateTime>',\n                                            '',\n                                        ),\n                                ).getTime() / 1000;\n                            break;\n                        default:\n                            break;\n                    }\n                }\n            }\n        });\n\n        // Delete old insert time as it's updated on each sync both paranet triples and private data after permissioned sync\n        await this.tripleStoreService.deletePublishTimestampMetadata(\n            TRIPLE_STORE_REPOSITORIES.DKG,\n            ual,\n        );\n\n        await this.tripleStoreService.insertKnowledgeCollection(\n            TRIPLE_STORE_REPOSITORIES.DKG,\n            ual,\n            data.assertion,\n            metadata,\n            5,\n            50,\n            paranetUAL,\n            contentType,\n        );\n    }\n\n    /**\n     * Syncs all states (\"merkle roots\") of a Knowledge Collection in a paranet.\n     *\n     * @param {string} paranetUAL Universal Asset Locator of the paranet\n     * @param {string} ual Universal Asset Locator of the Knowledge Collection\n     * @param {string} paranetId Id of paranet, stored on-chain. Provided in command options.\n     * @param {'OPEN'|'PERMISSIONED'} paranetNodesAccessPolicy Node access policy, enum string indicating paranet type.\n     * @param {string} operationId Local database id of sync operation. Needed for logging.\n     *\n     * @returns {Promise<null|Error>} Returns `null` if sync of all states was successful, otherwise `Error` which broke the operation.\n     */\n    async syncKc(paranetUAL, ual, paranetId, paranetNodesAccessPolicy, operationId) {\n        try {\n            this.logger.info(\n                `Paranet sync: Syncing asset: ${ual} for paranet: ${paranetId}, operation ID: ${operationId}`,\n            );\n\n            const { blockchain, contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n            const merkleRoots =\n                await this.blockchainModuleManager.getKnowledgeCollectionMerkleRoots(\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                );\n\n            for (let stateIndex = 0; stateIndex < merkleRoots.length; stateIndex += 1) {\n                this.logger.debug(\n                    `Paranet sync: Fetching state: ${merkleRoots[stateIndex].merkleRoot} index: ${\n                        stateIndex + 1\n                    } of ${merkleRoots.length} for asset with ual: ${ual}.`,\n                );\n\n                await this.syncKcState(\n                    paranetUAL,\n                    ual,\n                    stateIndex,\n                    merkleRoots[stateIndex].merkleRoot,\n                    paranetId,\n                    paranetNodesAccessPolicy,\n                );\n            }\n\n            await this.repositoryModuleManager.paranetKcMarkAsSynced(paranetUAL, ual);\n            return null;\n        } catch (error) {\n            this.logger.warn(\n                `Paranet sync: Failed to sync asset: ${ual} for paranet: ${paranetId}, error: ${error}`,\n            );\n\n            await this.repositoryModuleManager.paranetKcIncrementRetries(\n                paranetUAL,\n                ual,\n                `${error}`,\n            );\n            return error;\n        }\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to execute ${command.name}. Error: ${command.message}`);\n\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default paranetSyncCommands\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'paranetSyncCommands',\n            data: {},\n            transactional: false,\n            period: PARANET_SYNC_FREQUENCY_MILLS,\n            priority: COMMAND_PRIORITY.LOW,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default ParanetSyncCommand;\n"
  },
  {
    "path": "src/commands/paranet/start-paranet-sync-commands.js",
    "content": "import Command from '../command.js';\nimport {\n    ERROR_TYPE,\n    PARANET_SYNC_FREQUENCY_MILLS,\n    OPERATION_ID_STATUS,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass StartParanetSyncCommands extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.ualService = ctx.ualService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.paranetService = ctx.paranetService;\n\n        this.errorType = ERROR_TYPE.PARANET.START_PARANET_SYNC_ERROR;\n    }\n\n    async execute() {\n        const promises = [];\n        this.config.assetSync?.syncParanets.forEach(async (paranetUAL) => {\n            const operationId = this.operationIdService.generateId(\n                OPERATION_ID_STATUS.PARANET.PARANET_SYNC_START,\n            );\n\n            const { blockchain, contract, knowledgeCollectionId, knowledgeAssetId } =\n                this.ualService.resolveUAL(paranetUAL);\n\n            if (!knowledgeAssetId) {\n                this.logger.error(\n                    `Invalid paranet UAL: ${paranetUAL} . Knowledge asset token id is required!`,\n                );\n                return Command.empty();\n            }\n\n            const paranetId = this.paranetService.constructParanetId(\n                contract,\n                knowledgeCollectionId,\n                knowledgeAssetId,\n            );\n            const nodesAccessPolicy = await this.blockchainModuleManager.getNodesAccessPolicy(\n                blockchain,\n                paranetId,\n            );\n\n            const commandData = {\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n                knowledgeAssetId,\n                paranetUAL,\n                paranetId,\n                nodesAccessPolicy,\n                operationId,\n            };\n\n            promises.push(\n                this.commandExecutor.add({\n                    name: `paranetSyncCommand-${paranetId}`,\n                    data: commandData,\n                    period: PARANET_SYNC_FREQUENCY_MILLS,\n                }),\n            );\n        });\n\n        await Promise.all(promises);\n\n        return Command.empty();\n    }\n\n    /**\n     * Recover system from failure\n     * @param command\n     * @param error\n     */\n    async recover(command) {\n        this.logger.warn(`Failed to execute ${command.name}. Error: ${command.message}`);\n\n        return Command.repeat();\n    }\n\n    /**\n     * Builds default startParanetSyncCommands\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'startParanetSyncCommands',\n            data: {},\n            transactional: false,\n            priority: COMMAND_PRIORITY.LOW,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default StartParanetSyncCommands;\n"
  },
  {
    "path": "src/commands/protocols/ask/receiver/v1.0.0/v1-0-0-handle-ask-request-command.js",
    "content": "import HandleProtocolMessageCommand from '../../../common/handle-protocol-message-command.js';\nimport {\n    ERROR_TYPE,\n    NETWORK_MESSAGE_TYPES,\n    OPERATION_ID_STATUS,\n} from '../../../../../constants/constants.js';\n\nclass HandleAskRequestCommand extends HandleProtocolMessageCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.askService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.paranetService = ctx.paranetService;\n\n        this.errorType = ERROR_TYPE.ASK.ASK_REQUEST_REMOTE_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.ASK.ASK_REMOTE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.ASK.ASK_REMOTE_END;\n    }\n\n    async prepareMessage(commandData) {\n        const { ual, operationId, blockchain } = commandData;\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.ASK.ASK_REMOTE_START,\n        );\n\n        const knowledgeCollectionExistsInUnifiedGraph =\n            await this.tripleStoreService.checkIfKnowledgeCollectionExistsInUnifiedGraph(ual);\n        if (knowledgeCollectionExistsInUnifiedGraph) {\n            await this.operationService.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                knowledgeCollectionExistsInUnifiedGraph,\n                [\n                    OPERATION_ID_STATUS.ASK.ASK_FETCH_FROM_NODES_END,\n                    OPERATION_ID_STATUS.ASK.ASK_END,\n                    OPERATION_ID_STATUS.COMPLETED,\n                ],\n            );\n        }\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.ASK.ASK_REMOTE_END,\n        );\n\n        return knowledgeCollectionExistsInUnifiedGraph\n            ? {\n                  messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n                  messageData: { knowledgeCollectionExistsInUnifiedGraph },\n              }\n            : {\n                  messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                  messageData: { errorMessage: `Unable to find knowledge collection ${ual}` },\n              };\n    }\n\n    /**\n     * Builds default handleAskRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0HandleAskRequestCommand',\n            transactional: false,\n            errorType: ERROR_TYPE.ASK.ASK_REQUEST_REMOTE_ERROR,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default HandleAskRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/ask/sender/ask-find-shard-command.js",
    "content": "import FindShardCommand from '../../common/find-shard-command.js';\nimport { ERROR_TYPE, OPERATION_ID_STATUS } from '../../../../constants/constants.js';\n\nclass AskFindShardCommand extends FindShardCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.askService;\n\n        this.errorType = ERROR_TYPE.FIND_SHARD.ASK_FIND_SHARD_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.ASK.ASK_FIND_NODES_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.ASK.ASK_FIND_NODES_END;\n    }\n\n    // eslint-disable-next-line no-unused-vars\n    getOperationCommandSequence(nodePartOfShard, commandData) {\n        return [];\n    }\n\n    /**\n     * Builds default askFindShardCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'askFindShardCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default AskFindShardCommand;\n"
  },
  {
    "path": "src/commands/protocols/ask/sender/ask-schedule-messages-command.js",
    "content": "import ProtocolScheduleMessagesCommand from '../../common/protocol-schedule-messages-command.js';\nimport { OPERATION_ID_STATUS, ERROR_TYPE } from '../../../../constants/constants.js';\n\nclass AskScheduleMessagesCommand extends ProtocolScheduleMessagesCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.askService;\n\n        this.errorType = ERROR_TYPE.ASK.ASK_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.ASK.ASK_FETCH_FROM_NODES_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.ASK.ASK_FETCH_FROM_NODES_END;\n    }\n\n    getNextCommandData(command) {\n        return {\n            ...super.getNextCommandData(command),\n            ual: command.data.ual,\n            operationId: command.data.operationId,\n            minimumNumberOfNodeReplications: command.data.minimumNumberOfNodeReplications,\n        };\n    }\n\n    /**\n     * Builds default askScheduleMessagesCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'askScheduleMessagesCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default AskScheduleMessagesCommand;\n"
  },
  {
    "path": "src/commands/protocols/ask/sender/network-ask-command.js",
    "content": "import NetworkProtocolCommand from '../../common/network-protocol-command.js';\nimport { ERROR_TYPE } from '../../../../constants/constants.js';\n\nclass NetworkAskCommand extends NetworkProtocolCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.askService;\n        this.ualService = ctx.ualService;\n\n        this.errorType = ERROR_TYPE.ASK.ASK_NETWORK_ERROR;\n    }\n\n    /**\n     * Builds default networkGetCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'networkAskCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default NetworkAskCommand;\n"
  },
  {
    "path": "src/commands/protocols/ask/sender/v1.0.0/v1-0-0-ask-request-command.js",
    "content": "import ProtocolRequestCommand from '../../../common/protocol-request-command.js';\nimport {\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    ERROR_TYPE,\n    OPERATION_REQUEST_STATUS,\n    OPERATION_STATUS,\n} from '../../../../../constants/constants.js';\n\nclass AskRequestCommand extends ProtocolRequestCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.askService;\n        this.operationIdService = ctx.operationIdService;\n\n        this.errorType = ERROR_TYPE.ASK.ASK_REQUEST_ERROR;\n    }\n\n    async shouldSendMessage(command) {\n        const { operationId } = command.data;\n\n        const { status } = await this.operationService.getOperationStatus(operationId);\n\n        if (status === OPERATION_STATUS.IN_PROGRESS) {\n            return true;\n        }\n        this.logger.trace(\n            `${command.name} skipped for operationId: ${operationId} with status ${status}`,\n        );\n\n        return false;\n    }\n\n    async prepareMessage(command) {\n        const { ual, operationId, numberOfFoundNodes, blockchain } = command.data;\n\n        return {\n            ual,\n            operationId,\n            numberOfFoundNodes,\n            blockchain,\n        };\n    }\n\n    messageTimeout() {\n        return NETWORK_MESSAGE_TIMEOUT_MILLS.ASK.REQUEST;\n    }\n\n    async handleAck(command, responseData) {\n        if (responseData?.knowledgeCollectionExistsInUnifiedGraph) {\n            await this.operationService.processResponse(\n                command,\n                OPERATION_REQUEST_STATUS.COMPLETED,\n                responseData,\n            );\n\n            return ProtocolRequestCommand.empty();\n        }\n\n        return this.handleNack(command, responseData);\n    }\n\n    /**\n     * Builds default askRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0AskRequestCommand',\n            delay: 0,\n            retries: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default AskRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/find-curated-paranet-nodes-command.js",
    "content": "import Command from '../../command.js';\nimport { OPERATION_ID_STATUS } from '../../../constants/constants.js';\n\nclass FindCuratedParanetNodesCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.getService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.shardingTableService = ctx.shardingTableService;\n        this.cryptoService = ctx.cryptoService;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { operationId, blockchain, errorType, networkProtocols, paranetId, minAckResponses } =\n            command.data;\n\n        this.errorType = errorType;\n        this.logger.debug(\n            `Searching for paranet (${paranetId}) node(s) for operationId: ${operationId}`,\n        );\n\n        // TODO: protocol selection\n        const paranetNodes = [];\n        const foundNodes = await this.findNodes(blockchain, operationId, paranetId);\n        for (const node of foundNodes) {\n            if (node.id !== this.networkModuleManager.getPeerId().toB58String()) {\n                paranetNodes.push({ id: node.id, protocol: networkProtocols[0] });\n            }\n        }\n\n        this.logger.debug(\n            `Found ${paranetNodes.length} paranet (${paranetId}) node(s) for operationId: ${operationId}`,\n        );\n        this.logger.trace(\n            `Found paranet (${paranetId}) nodes: ${JSON.stringify(\n                paranetNodes.map((node) => node.id),\n                null,\n                2,\n            )}`,\n        );\n\n        if (paranetNodes.length < minAckResponses) {\n            await this.handleError(\n                operationId,\n                blockchain,\n                `Unable to find enough paranet (${paranetId}) nodes for operationId: ${operationId}. Minimum number of nodes required: ${minAckResponses}`,\n                this.errorType,\n                true,\n            );\n            return Command.empty();\n        }\n\n        return this.continueSequence(\n            {\n                ...command.data,\n                leftoverNodes: paranetNodes,\n                numberOfFoundNodes: paranetNodes.length,\n            },\n            command.sequence,\n        );\n    }\n\n    async findNodes(blockchainId, operationId, paranetId) {\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchainId,\n            OPERATION_ID_STATUS.FIND_CURATED_PARANET_NODES_START,\n        );\n\n        const paranetCuratedNodes = await this.blockchainModuleManager.getParanetCuratedNodes(\n            blockchainId,\n            paranetId,\n        );\n        const paranetCuratedPeerIds = paranetCuratedNodes.map((node) =>\n            this.cryptoService.convertHexToAscii(node.nodeId),\n        );\n\n        const paranetCuratedNodePeerRecords =\n            await this.repositoryModuleManager.getPeerRecordsByIds(\n                blockchainId,\n                paranetCuratedPeerIds,\n            );\n        const availableParanetNodes = paranetCuratedNodePeerRecords.filter(\n            (node) => node.lastSeen >= node.lastDialed,\n        );\n\n        const nodesFound = await Promise.all(\n            availableParanetNodes.map(({ peerId }) =>\n                this.shardingTableService.findPeerAddressAndProtocols(peerId),\n            ),\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchainId,\n            OPERATION_ID_STATUS.FIND_CURATED_PARANET_NODES_END,\n        );\n\n        return nodesFound;\n    }\n\n    /**\n     * Builds default findCuratedParanetNodesCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'findCuratedParanetNodesCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FindCuratedParanetNodesCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/find-shard-command.js",
    "content": "import Command from '../../command.js';\nimport { OPERATION_ID_STATUS, ERROR_TYPE } from '../../../constants/constants.js';\n\nclass FindShardCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.shardingTableService = ctx.shardingTableService;\n        this.errorType = ERROR_TYPE.FIND_SHARD.FIND_SHARD_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.FIND_NODES_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.FIND_NODES_END;\n    }\n\n    // eslint-disable-next-line no-unused-vars\n    getOperationCommandSequence(nodePartOfShard, commandData) {\n        return [];\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { operationId, blockchain, datasetRoot, minimumNumberOfNodeReplications } =\n            command.data;\n        this.logger.debug(\n            `Searching for shard for operationId: ${operationId}, dataset root: ${datasetRoot}`,\n        );\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationStartEvent,\n        );\n\n        this.minAckResponses = this.operationService.getMinAckResponses(\n            minimumNumberOfNodeReplications,\n        );\n\n        const networkProtocols = this.operationService.getNetworkProtocols();\n\n        const shardNodes = [];\n        let nodePartOfShard = false;\n        const currentPeerId = this.networkModuleManager.getPeerId().toB58String();\n\n        const foundNodes = await this.findShardNodes(blockchain);\n\n        for (const node of foundNodes) {\n            if (node.id === currentPeerId) {\n                nodePartOfShard = true;\n            } else {\n                shardNodes.push({ id: node.id, protocol: networkProtocols[0] });\n            }\n        }\n\n        const commandSequence = this.getOperationCommandSequence(nodePartOfShard, command.data);\n\n        command.sequence.push(...commandSequence);\n\n        this.logger.debug(\n            `Found ${\n                shardNodes.length + (nodePartOfShard ? 1 : 0)\n            } node(s) for operationId: ${operationId}`,\n        );\n        // TODO: Log local node\n        this.logger.trace(\n            `Found shard: ${JSON.stringify(\n                shardNodes.map((node) => node.id),\n                null,\n                2,\n            )}`,\n        );\n\n        if (shardNodes.length + (nodePartOfShard ? 1 : 0) < this.minAckResponses) {\n            await this.handleError(\n                operationId,\n                blockchain,\n                `Unable to find enough nodes for operationId: ${operationId}. Minimum number of nodes required: ${this.minAckResponses}`,\n                this.errorType,\n                true,\n            );\n            return Command.empty();\n        }\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationEndEvent,\n        );\n\n        return this.continueSequence(\n            {\n                ...command.data,\n                nodePartOfShard,\n                leftoverNodes: shardNodes,\n                numberOfFoundNodes: shardNodes.length + (nodePartOfShard ? 1 : 0),\n            },\n            command.sequence,\n        );\n    }\n\n    async findShardNodes(blockchainId) {\n        const shardNodes = await this.shardingTableService.findShard(\n            blockchainId,\n            true, // filter inactive nodes\n        );\n\n        // TODO: Optimize this so it's returned by shardingTableService.findShard\n        const nodesFound = await Promise.all(\n            shardNodes.map(({ peerId }) =>\n                this.shardingTableService.findPeerAddressAndProtocols(peerId),\n            ),\n        );\n\n        return nodesFound;\n    }\n\n    /**\n     * Builds default findShardCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'findShardCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FindShardCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/handle-protocol-message-command.js",
    "content": "import Command from '../../command.js';\nimport { NETWORK_MESSAGE_TYPES, OPERATION_ID_STATUS } from '../../../constants/constants.js';\n\nclass HandleProtocolMessageCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.ualService = ctx.ualService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.operationIdService = ctx.operationIdService;\n        this.shardingTableService = ctx.shardingTableService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n\n        this.operationStartEvent = OPERATION_ID_STATUS.HANDLE_PROTOCOL_MESSAGE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.HANDLE_PROTOCOL_MESSAGE_END;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { remotePeerId, operationId, protocol, blockchain } = command.data;\n\n        this.operationIdService.emitChangeEvent(this.operationStartEvent, operationId, blockchain);\n\n        try {\n            const { messageType, messageData } = await this.prepareMessage(command.data);\n\n            await this.networkModuleManager.sendMessageResponse(\n                protocol,\n                remotePeerId,\n                messageType,\n                operationId,\n                messageData,\n            );\n        } catch (error) {\n            if (command.retries) {\n                this.logger.warn(error.message);\n                return Command.retry();\n            }\n            await this.handleError(error.message, command);\n        }\n\n        this.networkModuleManager.removeCachedSession(operationId, remotePeerId);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationEndEvent,\n        );\n\n        return Command.empty();\n    }\n\n    async prepareMessage() {\n        throw Error('prepareMessage not implemented');\n    }\n\n    async validateShard(blockchain) {\n        const peerId = this.networkModuleManager.getPeerId().toB58String();\n        const isNodePartOfShard = await this.shardingTableService.isNodePartOfShard(\n            blockchain,\n            peerId,\n        );\n\n        return isNodePartOfShard;\n    }\n\n    async validateAssertionId(blockchain, contract, tokenId, assertionId, ual) {\n        const blockchainAssertionId =\n            await this.blockchainModuleManager.getKnowledgeCollectionMerkleRoot(\n                blockchain,\n                contract,\n                tokenId,\n            );\n        if (blockchainAssertionId !== assertionId) {\n            throw Error(\n                `Invalid assertion id for asset ${ual}. Received value from blockchain: ${blockchainAssertionId}, received value from request: ${assertionId}`,\n            );\n        }\n    }\n\n    async validateReceivedData(operationId, datasetRoot, dataset, blockchain, isOperationV0) {\n        this.logger.trace(`Validating shard for datasetRoot: ${datasetRoot}`);\n        const isShardValid = await this.validateShard(blockchain);\n        if (!isShardValid) {\n            this.logger.warn(\n                `Invalid shard on blockchain: ${blockchain}, operationId: ${operationId}`,\n            );\n            return {\n                messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                messageData: { errorMessage: 'Invalid neighbourhood' },\n            };\n        }\n\n        if (!isOperationV0) {\n            try {\n                await this.validationService.validateDatasetRoot(dataset, datasetRoot);\n            } catch (error) {\n                return {\n                    messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                    messageData: {\n                        errorMessage: error.message,\n                    },\n                };\n            }\n        }\n\n        return { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK, messageData: {} };\n    }\n\n    async handleError(errorMessage, command) {\n        const { operationId, blockchain, remotePeerId, protocol } = command.data;\n\n        this.logger.error(`Command error (${this.errorType}): ${errorMessage}`);\n        if (errorMessage !== null) {\n            this.logger.debug(`Marking operation id ${operationId} as failed`);\n            await this.operationIdService.removeOperationIdCache(operationId);\n        }\n        this.operationIdService.emitChangeEvent(\n            this.errorType,\n            operationId,\n            blockchain,\n            errorMessage,\n            this.errorType,\n        );\n\n        try {\n            await this.networkModuleManager.sendMessageResponse(\n                protocol,\n                remotePeerId,\n                NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                operationId,\n                { errorMessage },\n            );\n        } catch (sendErr) {\n            this.logger.debug(\n                `Failed to send NACK to ${remotePeerId} for operation ${operationId}: ${sendErr.message}`,\n            );\n        }\n        this.networkModuleManager.removeCachedSession(operationId, remotePeerId);\n    }\n}\n\nexport default HandleProtocolMessageCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/network-protocol-command.js",
    "content": "import Command from '../../command.js';\nimport { ERROR_TYPE } from '../../../constants/constants.js';\n\nclass NetworkProtocolCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n\n        this.errorType = ERROR_TYPE.NETWORK_PROTOCOL_ERROR;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { minimumNumberOfNodeReplications, batchSize } = command.data;\n\n        const batchSizePar = this.operationService.getBatchSize(batchSize);\n        const minAckResponses = this.operationService.getMinAckResponses(\n            minimumNumberOfNodeReplications,\n        );\n\n        const commandSequence = [\n            `${this.operationService.getOperationName()}ScheduleMessagesCommand`,\n        ];\n\n        await this.commandExecutor.add({\n            name: commandSequence[0],\n            sequence: commandSequence.slice(1),\n            delay: 0,\n            data: {\n                ...command.data,\n                batchSize: batchSizePar,\n                minAckResponses,\n                errorType: this.errorType,\n            },\n            transactional: false,\n        });\n\n        return Command.empty();\n    }\n\n    getBatchSize() {\n        throw Error('getBatchSize not implemented');\n    }\n\n    getMinAckResponses() {\n        throw Error('getMinAckResponses not implemented');\n    }\n\n    /**\n     * Builds default protocolNetworkCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'protocolNetworkCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default NetworkProtocolCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/protocol-message-command.js",
    "content": "import Command from '../../command.js';\nimport { NETWORK_MESSAGE_TYPES, OPERATION_REQUEST_STATUS } from '../../../constants/constants.js';\n\nclass ProtocolMessageCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.networkModuleManager = ctx.networkModuleManager;\n    }\n\n    async executeProtocolMessageCommand(command, messageType) {\n        if (!(await this.shouldSendMessage(command))) {\n            return Command.empty();\n        }\n\n        const message = await this.prepareMessage(command);\n\n        return this.sendProtocolMessage(command, message, messageType);\n    }\n\n    async shouldSendMessage() {\n        return true;\n    }\n\n    async prepareMessage() {\n        throw Error('prepareMessage not implemented');\n    }\n\n    async sendProtocolMessage(command, message, messageType) {\n        const { node, operationId } = command.data;\n\n        const response = await this.networkModuleManager.sendMessage(\n            node.protocol,\n            node.id,\n            messageType,\n            operationId,\n            message,\n            this.messageTimeout(),\n        );\n\n        this.networkModuleManager.removeCachedSession(operationId, node.id);\n\n        switch (response.header.messageType) {\n            case NETWORK_MESSAGE_TYPES.RESPONSES.BUSY:\n                return this.handleBusy(command, response.data);\n            case NETWORK_MESSAGE_TYPES.RESPONSES.NACK:\n                return this.handleNack(command, response.data);\n            case NETWORK_MESSAGE_TYPES.RESPONSES.ACK:\n                return this.handleAck(command, response.data);\n            default:\n                await this.markResponseAsFailed(\n                    command,\n                    `Received unknown message type from node during ${command.name}`,\n                );\n                return Command.empty();\n        }\n    }\n\n    messageTimeout() {\n        throw Error('messageTimeout not implemented');\n    }\n\n    async handleAck(command) {\n        return this.continueSequence(command.data, command.sequence);\n    }\n\n    async handleBusy() {\n        return Command.retry();\n    }\n\n    async handleNack(command, responseData) {\n        await this.markResponseAsFailed(\n            command,\n            `Received NACK response from node during ${command.name}. Error message: ${responseData.errorMessage}`,\n        );\n        return Command.empty();\n    }\n\n    async recover(command) {\n        const { node, operationId } = command.data;\n        this.networkModuleManager.removeCachedSession(operationId, node.id);\n\n        await this.markResponseAsFailed(command, command.message);\n        return Command.empty();\n    }\n\n    async markResponseAsFailed(command, errorMessage) {\n        await this.operationService.processResponse(command, OPERATION_REQUEST_STATUS.FAILED, {\n            errorMessage,\n        });\n    }\n\n    async retryFinished(command) {\n        await this.markResponseAsFailed(\n            command,\n            `Max number of retries for protocol message ${command.name} reached`,\n        );\n    }\n}\n\nexport default ProtocolMessageCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/protocol-request-command.js",
    "content": "import Command from '../../command.js';\nimport ProtocolMessageCommand from './protocol-message-command.js';\nimport { NETWORK_MESSAGE_TYPES, OPERATION_REQUEST_STATUS } from '../../../constants/constants.js';\n\nclass ProtocolRequestCommand extends ProtocolMessageCommand {\n    async execute(command) {\n        const result = await this.executeProtocolMessageCommand(\n            command,\n            NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST,\n        );\n\n        return result;\n    }\n\n    async handleAck(command, responseData) {\n        await this.operationService.processResponse(\n            command,\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            responseData,\n        );\n        return Command.empty();\n    }\n}\n\nexport default ProtocolRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/protocol-schedule-messages-command.js",
    "content": "import Command from '../../command.js';\nimport { OPERATION_ID_STATUS } from '../../../constants/constants.js';\n\nclass ProtocolScheduleMessagesCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.protocolService = ctx.protocolService;\n\n        this.operationStartEvent = OPERATION_ID_STATUS.PROTOCOL_SCHEDULE_MESSAGE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.PROTOCOL_SCHEDULE_MESSAGE_END;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const {\n            operationId,\n            batchSize,\n            leftoverNodes,\n            numberOfFoundNodes,\n            blockchain,\n            minAckResponses,\n        } = command.data;\n\n        const currentBatchNodes = leftoverNodes.slice(0, batchSize);\n        const currentBatchLeftoverNodes =\n            batchSize < leftoverNodes.length ? leftoverNodes.slice(batchSize) : [];\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationStartEvent,\n        );\n\n        this.logger.debug(\n            `Trying to ${this.operationService.getOperationName()} to batch of ${\n                currentBatchNodes.length\n            }, leftover for retry: ${currentBatchLeftoverNodes.length}`,\n        );\n        const nextCommandData = this.getNextCommandData(command);\n\n        const addCommandPromises = currentBatchNodes.map(async (node) => {\n            const commandSequence = this.protocolService.getSenderCommandSequence(node.protocol);\n            await this.commandExecutor.add({\n                name: commandSequence[0],\n                sequence: commandSequence.slice(1),\n                delay: 0,\n                data: {\n                    ...nextCommandData,\n                    blockchain,\n                    operationId,\n                    node,\n                    numberOfFoundNodes,\n                    batchSize,\n                    minAckResponses,\n                    leftoverNodes: currentBatchLeftoverNodes,\n                },\n                period: 5000,\n                retries: 3,\n                transactional: false,\n            });\n        });\n\n        await Promise.all(addCommandPromises);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationEndEvent,\n        );\n\n        return Command.empty();\n    }\n\n    getNextCommandData(command) {\n        const { datasetRoot, blockchain } = command.data;\n        return {\n            blockchain,\n            datasetRoot,\n        };\n    }\n\n    /**\n     * Builds default protocolScheduleMessagesCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'protocolScheduleMessagesCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default ProtocolScheduleMessagesCommand;\n"
  },
  {
    "path": "src/commands/protocols/common/validate-assertion-metadata-command.js",
    "content": "import Command from '../../command.js';\nimport { OPERATION_ID_STATUS } from '../../../constants/constants.js';\n\nclass ValidateAssertionMetadataCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.dataService = ctx.dataService;\n        this.operationStartEvent = OPERATION_ID_STATUS.VALIDATE_ASSERTION_METADATA_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.VALIDATE_ASSERTION_METADATA_END;\n    }\n\n    async execute(command) {\n        const { operationId, ual, blockchain, merkleRoot, cachedMerkleRoot, byteSize, assertion } =\n            command.data;\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationStartEvent,\n        );\n\n        try {\n            if (merkleRoot !== cachedMerkleRoot) {\n                await this.handleError(\n                    operationId,\n                    blockchain,\n                    `Invalid Merkle Root for Knowledge Collection with UAL: ${ual}. Received value from blockchain: ${merkleRoot}, Cached value from publish operation: ${cachedMerkleRoot}`,\n                    this.errorType,\n                    true,\n                );\n            }\n\n            const calculatedAssertionSize = this.dataService.calculateAssertionSize(\n                assertion.public ?? assertion,\n            );\n\n            if (byteSize.toString() !== calculatedAssertionSize.toString()) {\n                await this.handleError(\n                    operationId,\n                    blockchain,\n                    `Invalid Assertion Size for Knowledge Collection with UAL: ${ual}. Received value from blockchain: ${byteSize}, Calculated value: ${calculatedAssertionSize}`,\n                    this.errorType,\n                    true,\n                );\n            }\n        } catch (e) {\n            await this.handleError(operationId, blockchain, e.message, this.errorType, true);\n            return Command.empty();\n        }\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            this.operationEndEvent,\n        );\n\n        return this.continueSequence(command.data, command.sequence);\n    }\n\n    /**\n     * Builds default validateAssertionMetadataCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'validateAssertionMetadataCommand',\n            delay: 0,\n            retries: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default ValidateAssertionMetadataCommand;\n"
  },
  {
    "path": "src/commands/protocols/finality/receiver/publish-finality-save-ack-command.js",
    "content": "import {\n    COMMAND_PRIORITY,\n    NETWORK_MESSAGE_TYPES,\n    OPERATION_ID_STATUS,\n} from '../../../../constants/constants.js';\nimport Command from '../../../command.js';\n\nclass PublishFinalitySaveAckCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.protocolService = ctx.protocolService;\n        this.operationService = ctx.finalityService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { ual, publishOperationId, blockchain, operationId, remotePeerId, state } =\n            command.data;\n\n        let ualWithState = ual;\n        if (state) {\n            ualWithState = `${ual}:${state}`;\n        }\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.FINALITY.PUBLISH_FINALITY_REMOTE_START,\n        );\n\n        let response;\n        let success;\n        try {\n            await this.repositoryModuleManager.saveFinalityAck(\n                publishOperationId,\n                ualWithState,\n                remotePeerId,\n            );\n\n            success = true;\n            response = {\n                messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n                messageData: { message: `Acknowledged storing of ${ualWithState}.` },\n            };\n        } catch (err) {\n            success = false;\n            response = {\n                messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                messageData: { errorMessage: `Failed to acknowledge storing of ${ualWithState}.` },\n            };\n        }\n\n        await this.operationService.markOperationAsCompleted(operationId, blockchain, success, [\n            OPERATION_ID_STATUS.FINALITY.PUBLISH_FINALITY_REMOTE_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ]);\n\n        return this.continueSequence({ ...command.data, response }, command.sequence);\n    }\n\n    /**\n     * Builds default publishFinalitySaveAckCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'publishFinalitySaveAckCommand',\n            delay: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PublishFinalitySaveAckCommand;\n"
  },
  {
    "path": "src/commands/protocols/finality/receiver/v1.0.0/v1-0-0-handle-finality-request-command.js",
    "content": "import HandleProtocolMessageCommand from '../../../common/handle-protocol-message-command.js';\nimport {\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    COMMAND_PRIORITY,\n    NETWORK_MESSAGE_TYPES,\n} from '../../../../../constants/constants.js';\n\nclass HandleFinalityRequestCommand extends HandleProtocolMessageCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.finalityService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.paranetService = ctx.paranetService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.commandExecutor = ctx.commandExecutor;\n        this.protocolService = ctx.protocolService;\n        this.operationService = ctx.finalityService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n\n        this.errorType = ERROR_TYPE.FINALITY.FINALITY_REQUEST_REMOTE_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.FINALITY.FINALITY_REMOTE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.FINALITY.FINALITY_REMOTE_END;\n    }\n\n    async prepareMessage(commandData) {\n        return commandData.response;\n    }\n\n    async execute(command) {\n        const { ual, publishOperationId, blockchain, operationId, remotePeerId, state } =\n            command.data;\n\n        let ualWithState = ual;\n        if (state) {\n            ualWithState = `${ual}:${state}`;\n        }\n\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.FINALITY.PUBLISH_FINALITY_REMOTE_START,\n            operationId,\n            blockchain,\n        );\n\n        let response;\n        let success;\n        try {\n            await this.repositoryModuleManager.saveFinalityAck(\n                publishOperationId,\n                ualWithState,\n                remotePeerId,\n            );\n\n            success = true;\n            response = {\n                messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n                messageData: { message: `Acknowledged storing of ${ualWithState}.` },\n            };\n        } catch (err) {\n            success = false;\n            response = {\n                messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                messageData: { errorMessage: `Failed to acknowledge storing of ${ualWithState}.` },\n            };\n        }\n\n        await this.operationService.markOperationAsCompleted(operationId, blockchain, success, [\n            OPERATION_ID_STATUS.FINALITY.PUBLISH_FINALITY_FETCH_FROM_NODES_END,\n            OPERATION_ID_STATUS.FINALITY.PUBLISH_FINALITY_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ]);\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.FINALITY.PUBLISH_FINALITY_REMOTE_END,\n            operationId,\n            blockchain,\n        );\n\n        // eslint-disable-next-line no-param-reassign\n        command.data.response = response;\n        super.execute(command);\n\n        return HandleFinalityRequestCommand.empty();\n    }\n\n    /**\n     * Builds default handleFinalityRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0HandleFinalityRequestCommand',\n            delay: 0,\n            transactional: false,\n            errorType: ERROR_TYPE.FINALITY.FINALITY_REQUEST_REMOTE_ERROR,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default HandleFinalityRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/finality/sender/finality-schedule-messages-command.js",
    "content": "import ProtocolScheduleMessagesCommand from '../../common/protocol-schedule-messages-command.js';\nimport {\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    COMMAND_PRIORITY,\n} from '../../../../constants/constants.js';\n\nclass FinalityScheduleMessagesCommand extends ProtocolScheduleMessagesCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.finalityService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n\n        this.operationStartEvent = OPERATION_ID_STATUS.FINALITY.FINALITY_REPLICATE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.FINALITY.FINALITY_REPLICATE_END;\n        this.errorType = ERROR_TYPE.FINALITY.FINALITY_START_ERROR;\n    }\n\n    getNextCommandData(command) {\n        return {\n            ...super.getNextCommandData(command),\n            ual: command.data.ual,\n            publishOperationId: command.data.publishOperationId,\n        };\n    }\n\n    /**\n     * Builds default finalityScheduleMessagesCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'finalityScheduleMessagesCommand',\n            delay: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FinalityScheduleMessagesCommand;\n"
  },
  {
    "path": "src/commands/protocols/finality/sender/find-publisher-node-command.js",
    "content": "import { COMMAND_PRIORITY } from '../../../../constants/constants.js';\nimport Command from '../../../command.js';\n\nclass FindPublisherNodeCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.finalityService;\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { remotePeerId } = command.data;\n\n        const networkProtocols = this.operationService.getNetworkProtocols();\n        const leftoverNodes = [{ id: remotePeerId, protocol: networkProtocols[0] }];\n\n        return this.continueSequence(\n            {\n                ...command.data,\n                leftoverNodes,\n                numberOfFoundNodes: leftoverNodes.length,\n            },\n            command.sequence,\n        );\n    }\n\n    /**\n     * Builds default findPublisherNodeCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'findPublisherNodeCommand',\n            delay: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FindPublisherNodeCommand;\n"
  },
  {
    "path": "src/commands/protocols/finality/sender/network-finality-command.js",
    "content": "import Command from '../../../command.js';\nimport NetworkProtocolCommand from '../../common/network-protocol-command.js';\nimport {\n    COMMAND_PRIORITY,\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n} from '../../../../constants/constants.js';\n\nclass NetworkFinalityCommand extends NetworkProtocolCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.finalityService;\n        this.ualService = ctx.ualService;\n\n        this.errorType = ERROR_TYPE.FINALITY.FINALITY_NETWORK_ERROR;\n    }\n\n    async execute(command) {\n        await super.execute(command);\n\n        const { operationId, blockchain } = command.data;\n\n        await this.operationService.markOperationAsCompleted(operationId, blockchain, null, [\n            OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_END,\n        ]);\n\n        return Command.empty();\n    }\n\n    /**\n     * Builds default networkFinalityCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'networkFinalityCommand',\n            delay: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default NetworkFinalityCommand;\n"
  },
  {
    "path": "src/commands/protocols/finality/sender/v1.0.0/v1-0-0-finality-request-command.js",
    "content": "import Command from '../../../../command.js';\nimport ProtocolRequestCommand from '../../../common/protocol-request-command.js';\nimport {\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    COMMAND_PRIORITY,\n} from '../../../../../constants/constants.js';\n\nclass FinalityRequestCommand extends ProtocolRequestCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.finalityService;\n        this.operationIdService = ctx.operationIdService;\n\n        this.errorType = ERROR_TYPE.FINALITY.FINALITY_REQUEST_ERROR;\n    }\n\n    async prepareMessage(command) {\n        const { ual, publishOperationId, blockchain, operationId } = command.data;\n\n        return { ual, publishOperationId, blockchain, operationId };\n    }\n\n    async handleAck(command) {\n        const { operationId, blockchain } = command.data;\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.COMPLETED,\n        );\n        return ProtocolRequestCommand.empty();\n    }\n\n    async handleNack(command, responseData) {\n        const { operationId, blockchain } = command.data;\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.COMPLETED,\n        );\n        await this.markResponseAsFailed(\n            command,\n            `Received NACK response from node during ${command.name}. Error message: ${responseData.errorMessage}`,\n        );\n        return Command.empty();\n    }\n\n    messageTimeout() {\n        return NETWORK_MESSAGE_TIMEOUT_MILLS.FINALITY.REQUEST;\n    }\n\n    /**\n     * Builds default finalityRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0FinalityRequestCommand',\n            delay: 0,\n            retries: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default FinalityRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/get/receiver/v1.0.0/v1-0-0-handle-batch-get-request-command.js",
    "content": "import fs from 'fs/promises';\nimport path from 'path';\nimport HandleProtocolMessageCommand from '../../../common/handle-protocol-message-command.js';\nimport {\n    ERROR_TYPE,\n    NETWORK_MESSAGE_TYPES,\n    OPERATION_ID_STATUS,\n    MIGRATION_FLAG_PATH,\n    TRIPLE_STORE_REPOSITORY,\n    TRIPLES_VISIBILITY,\n    BATCH_GET_UAL_MAX_LIMIT,\n    COMMAND_PRIORITY,\n} from '../../../../../constants/constants.js';\n\nclass HandleBatchGetRequestCommand extends HandleProtocolMessageCommand {\n    constructor(ctx) {\n        super(ctx);\n\n        this.logger = ctx.config.logging.enableExperimentalScopes\n            ? ctx.logger.child({\n                  scope: 'HandleBatchGetRequestCommand',\n              })\n            : ctx.logger;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.paranetService = ctx.paranetService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.cryptoService = ctx.cryptoService;\n\n        this.errorType = ERROR_TYPE.BATCH_GET.BATCH_GET_REQUEST_REMOTE_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_REMOTE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_REMOTE_END;\n    }\n\n    async prepareMessage(commandData) {\n        const { operationId, blockchain, includeMetadata } = commandData;\n        let { uals, tokenIds } = commandData;\n\n        this.logger.startTimer(\n            `HandleBatchGetRequestCommand [PREPARE]: ${operationId} ${uals.length}`,\n        );\n        await this.operationIdService.emitChangeEvent(\n            this.operationStartEvent,\n            operationId,\n            blockchain,\n        );\n\n        // Trim uals and tokenIds to the max limit of BATCH_GET_UAL_MAX_LIMIT\n        uals = uals.slice(0, BATCH_GET_UAL_MAX_LIMIT);\n        tokenIds = Object.fromEntries(Object.entries(tokenIds).slice(0, BATCH_GET_UAL_MAX_LIMIT));\n\n        const promises = [];\n\n        let migrationFlag = '0';\n        const migrationFlagPath = path.join(process.cwd(), MIGRATION_FLAG_PATH);\n        try {\n            migrationFlag = await fs.readFile(migrationFlagPath, 'utf8');\n            migrationFlag = migrationFlag.trim();\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                this.logger.warn(\n                    `Migration flag file not found at ${migrationFlagPath}, using default value '${migrationFlag}'`,\n                );\n            } else {\n                throw error;\n            }\n        }\n\n        this.logger.endTimer(\n            `HandleBatchGetRequestCommand [PREPARE]: ${operationId} ${uals.length}`,\n        );\n\n        this.logger.startTimer(\n            `HandleBatchGetRequestCommand [PROCESSING]: ${operationId} ${uals.length}`,\n        );\n\n        const assertionPromise = this.tripleStoreService.getAssertionsInBatch(\n            TRIPLE_STORE_REPOSITORY.DKG,\n            uals,\n            tokenIds,\n            TRIPLES_VISIBILITY.PUBLIC,\n            operationId,\n        );\n\n        promises.push(assertionPromise);\n\n        if (includeMetadata) {\n            const metadataPromise = this.tripleStoreService.getAssertionMetadataBatch(\n                uals,\n                tokenIds,\n            );\n            promises.push(metadataPromise);\n        }\n\n        const [assertions, metadata] = await Promise.all(promises);\n\n        const responseData = {\n            assertions,\n            ...(includeMetadata && metadata && { metadata }),\n        };\n\n        this.logger.endTimer(\n            `HandleBatchGetRequestCommand [PROCESSING]: ${operationId} ${uals.length}`,\n        );\n\n        this.logger.startTimer(\n            `HandleBatchGetRequestCommand [RESPONSE]: ${operationId} ${uals.length}`,\n        );\n\n        if (assertions?.length) {\n            await this.operationIdService.emitChangeEvent(\n                this.operationEndEvent,\n                operationId,\n                blockchain,\n            );\n        }\n\n        this.logger.endTimer(\n            `HandleBatchGetRequestCommand [RESPONSE]: ${operationId} ${uals.length}`,\n        );\n\n        return { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK, messageData: responseData };\n    }\n\n    /**\n     * Builds default handleGetRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0HandleBatchGetRequestCommand',\n            transactional: false,\n            priority: COMMAND_PRIORITY.MEDIUM,\n            errorType: this.errorType,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default HandleBatchGetRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/get/receiver/v1.0.0/v1-0-0-handle-get-request-command.js",
    "content": "import HandleProtocolMessageCommand from '../../../common/handle-protocol-message-command.js';\nimport {\n    ERROR_TYPE,\n    NETWORK_MESSAGE_TYPES,\n    OPERATION_ID_STATUS,\n    TRIPLES_VISIBILITY,\n    PARANET_ACCESS_POLICY,\n    COMMAND_PRIORITY,\n} from '../../../../../constants/constants.js';\n\nclass HandleGetRequestCommand extends HandleProtocolMessageCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.getService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.paranetService = ctx.paranetService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.cryptoService = ctx.cryptoService;\n\n        this.errorType = ERROR_TYPE.GET.GET_REQUEST_REMOTE_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.GET.GET_REMOTE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.GET.GET_REMOTE_END;\n    }\n\n    async prepareMessage(commandData) {\n        const {\n            operationId,\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            knowledgeAssetId,\n            tokenIds,\n            ual,\n            includeMetadata,\n            paranetUAL,\n            remotePeerId,\n            migrationFlag,\n            repository,\n        } = commandData;\n\n        if (paranetUAL) {\n            const {\n                contract: paranetContract,\n                knowledgeCollectionId: paranetKnowledgeCollectionId,\n                knowledgeAssetId: paranetKnowledgeAssetId,\n            } = this.ualService.resolveUAL(paranetUAL);\n            const paranetId = this.paranetService.constructParanetId(\n                paranetContract,\n                paranetKnowledgeCollectionId,\n                paranetKnowledgeAssetId,\n            );\n            const paranetNodeAccessPolicy = await this.blockchainModuleManager.getNodesAccessPolicy(\n                blockchain,\n                paranetId,\n            );\n            if (paranetNodeAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n                const knowledgeCollectionOnchainId = this.cryptoService.keccak256EncodePacked(\n                    ['address', 'uint256'],\n                    [contract, knowledgeCollectionId],\n                );\n                const [isKCInParanet, paranetPermissionedNodes] = await Promise.all([\n                    this.blockchainModuleManager.isKnowledgeCollectionRegistered(\n                        blockchain,\n                        paranetId,\n                        knowledgeCollectionOnchainId,\n                    ),\n                    this.blockchainModuleManager.getPermissionedNodes(blockchain, paranetId),\n                ]);\n\n                if (!isKCInParanet) {\n                    return {\n                        messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                        messageData: {\n                            errorMessage: `Knowledge collection ${knowledgeCollectionId} is not registered in the Paranet (${paranetId}) with UAL: ${paranetUAL}`,\n                        },\n                    };\n                }\n                const paranetPermissionedPeerIds = paranetPermissionedNodes.map((node) =>\n                    this.cryptoService.convertHexToAscii(node.nodeId),\n                );\n\n                if (!paranetPermissionedPeerIds.includes(remotePeerId)) {\n                    return {\n                        messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                        messageData: {\n                            errorMessage: `Remote peer ${remotePeerId} is not a part of the Paranet (${paranetId}) with UAL: ${paranetUAL}`,\n                        },\n                    };\n                }\n\n                const currentPeerId = this.networkModuleManager.getPeerId().toB58String();\n                if (!paranetPermissionedPeerIds.includes(currentPeerId)) {\n                    return {\n                        messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                        messageData: {\n                            errorMessage: `This node is not a part of the Paranet (${paranetId}) with UAL: ${paranetUAL}`,\n                        },\n                    };\n                }\n                const promises = [];\n                promises.push(\n                    this.tripleStoreService.getAssertion(\n                        blockchain,\n                        contract,\n                        knowledgeCollectionId,\n                        knowledgeAssetId,\n                        tokenIds,\n                        migrationFlag,\n                        TRIPLES_VISIBILITY.ALL,\n                        repository,\n                    ),\n                );\n\n                if (includeMetadata) {\n                    const metadataPromise = this.tripleStoreService.getAssertionMetadata(\n                        blockchain,\n                        contract,\n                        knowledgeCollectionId,\n                    );\n                    promises.push(metadataPromise);\n                }\n\n                const [assertion, metadata] = await Promise.all(promises);\n\n                if (assertion?.public?.length) {\n                    return {\n                        messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n                        messageData: { assertion, metadata },\n                    };\n                }\n\n                return {\n                    messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                    messageData: {\n                        errorMessage: `Unable to find assertion ${ual} for Paranet (${paranetId}) with UAL: ${paranetUAL}`,\n                    },\n                };\n            }\n        }\n\n        const promises = [];\n\n        const assertionPromise = this.tripleStoreService.getAssertion(\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            knowledgeAssetId,\n            tokenIds,\n            migrationFlag,\n            TRIPLES_VISIBILITY.PUBLIC,\n            repository,\n        );\n\n        promises.push(assertionPromise);\n\n        if (includeMetadata) {\n            const metadataPromise = this.tripleStoreService.getAssertionMetadata(\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n            );\n            promises.push(metadataPromise);\n        }\n\n        const [assertion, metadata] = await Promise.all(promises);\n\n        const responseData = {\n            assertion,\n            ...(includeMetadata && metadata && { metadata }),\n        };\n\n        if (assertion?.public?.length || assertion?.length) {\n            await this.operationIdService.emitChangeEvent(\n                this.operationEndEvent,\n                operationId,\n                blockchain,\n            );\n        }\n\n        return assertion?.public?.length || assertion?.length\n            ? { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK, messageData: responseData }\n            : {\n                  messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                  messageData: { errorMessage: `Unable to find assertion ${ual}` },\n              };\n    }\n\n    /**\n     * Builds default handleGetRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0HandleGetRequestCommand',\n            delay: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGH,\n            errorType: ERROR_TYPE.GET.GET_REQUEST_REMOTE_ERROR,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default HandleGetRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/get/sender/batch-get-command.js",
    "content": "import { kcTools } from 'assertion-tools';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport Command from '../../../command.js';\nimport {\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    TRIPLE_STORE_REPOSITORIES,\n    NETWORK_MESSAGE_TYPES,\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    MIGRATION_FLAG_PATH,\n    PRIVATE_HASH_SUBJECT_PREFIX,\n    OPERATION_STATUS,\n    BATCH_GET_BATCH_SIZE as BATCH_SIZE,\n    TRIPLE_STORE_REPOSITORY,\n    TRIPLES_VISIBILITY,\n    COMMAND_PRIORITY,\n} from '../../../../constants/constants.js';\n\nclass BatchGetCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n\n        this.logger = ctx.config.logging.enableExperimentalScopes\n            ? ctx.logger.child({\n                  scope: 'BatchGetCommand',\n              })\n            : ctx.logger;\n        this.operationIdService = ctx.operationIdService;\n        this.ualService = ctx.ualService;\n        this.operationService = ctx.batchGetService;\n        this.validationService = ctx.validationService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.paranetService = ctx.paranetService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.shardingTableService = ctx.shardingTableService;\n        this.cryptoService = ctx.cryptoService;\n        this.messagingService = ctx.messagingService;\n        this.tripleStoreModuleManager = ctx.tripleStoreModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.DONE_THRESHOLD = ctx.config.assetSync.syncDKG.doneThreshold;\n    }\n\n    async handleError(operationId, blockchain, errorMessage, errorType) {\n        await this.operationService.markOperationAsFailed(\n            operationId,\n            blockchain,\n            errorMessage,\n            errorType,\n        );\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_FAILED,\n            operationId,\n            blockchain,\n            errorMessage,\n            errorType,\n        );\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const {\n            operationId,\n            blockchain,\n            uals,\n            // paranetUAL,\n            // paranetSync,\n            contentType,\n            includeMetadata,\n            paranetNodesAccessPolicy,\n        } = command.data;\n\n        this.logger.startTimer(`BatchGetCommand [PREPARE]: ${operationId} ${uals.length}`);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_START,\n        );\n\n        await this.repositoryModuleManager.createOperationRecord(\n            this.operationService.getOperationName(),\n            operationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_VALIDATE_ASSET_START,\n        );\n\n        const { isValid, errorMessage } = await this.validateUALs(operationId, blockchain, uals);\n\n        this.logger.endTimer(`BatchGetCommand [PREPARE]: ${operationId} ${uals.length}`);\n\n        if (!isValid) {\n            await this.handleError(\n                operationId,\n                blockchain,\n                errorMessage,\n                ERROR_TYPE.BATCH_GET.BATCH_GET_VALIDATE_ASSET_ERROR,\n            );\n            return Command.empty();\n        }\n\n        this.logger.startTimer(`BatchGetCommand [NETWORK_INIT]: ${operationId} ${uals.length}`);\n\n        const currentPeerId = this.networkModuleManager.getPeerId().toB58String();\n        // let paranetId;\n        const repository = TRIPLE_STORE_REPOSITORIES.DKG;\n        let migrationFlag = '0';\n        const migrationFlagPath = path.join(process.cwd(), MIGRATION_FLAG_PATH);\n        try {\n            migrationFlag = await fs.readFile(migrationFlagPath, 'utf8');\n            migrationFlag = migrationFlag.trim();\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                this.logger.warn(\n                    `Migration flag file not found at ${migrationFlagPath}, using default value '${migrationFlag}'`,\n                );\n            } else {\n                throw error;\n            }\n        }\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_VALIDATE_ASSET_END,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_LOCAL_START,\n        );\n\n        this.logger.endTimer(`BatchGetCommand [NETWORK_INIT]: ${operationId} ${uals.length}`);\n\n        this.logger.startTimer(`BatchGetCommand [TOKEN_IDS]: ${operationId} ${uals.length}`);\n\n        const tokenIds = {};\n\n        const tokenIdPromises = uals.map(async (ual) => {\n            const { contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n            try {\n                tokenIds[ual] = await this.blockchainModuleManager.getKnowledgeAssetsRange(\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                );\n            } catch (error) {\n                // Asset created on old content asset storage contract\n                tokenIds[ual] = {\n                    startTokenId: 1,\n                    endTokenId: 1,\n                    burned: [],\n                };\n            }\n        });\n\n        await Promise.all(tokenIdPromises);\n\n        this.logger.endTimer(`BatchGetCommand [TOKEN_IDS]: ${operationId} ${uals.length}`);\n\n        this.logger.startTimer(`BatchGetCommand [LOCAL_BATCH_GET]: ${operationId} ${uals.length}`);\n\n        const promises = [];\n        const assertionPromise = this.tripleStoreService.getAssertionsInBatch(\n            TRIPLE_STORE_REPOSITORY.DKG,\n            uals,\n            tokenIds,\n            TRIPLES_VISIBILITY.PUBLIC,\n            operationId,\n        );\n        promises.push(assertionPromise);\n\n        const [batchAssertions] = await Promise.all(promises);\n\n        const finalResult = { local: [], remote: {}, metadata: {} };\n\n        this.logger.endTimer(`BatchGetCommand [LOCAL_BATCH_GET]: ${operationId} ${uals.length}`);\n\n        this.logger.startTimer(\n            `BatchGetCommand [LOCAL_BATCH_GET_VALIDATE]: ${operationId} ${uals.length}`,\n        );\n\n        const localGetResultValid = await this.validateBatchResponse(\n            batchAssertions,\n            blockchain,\n            paranetNodesAccessPolicy,\n            contentType,\n            finalResult,\n        );\n\n        // Filter what we have locally and add those ual to finalResult local\n        const ualPresentLocally = Object.keys(localGetResultValid).filter(\n            (ual) => localGetResultValid[ual],\n        );\n        const ualNotPresentLocally = Object.keys(localGetResultValid).filter(\n            (ual) => !localGetResultValid[ual],\n        );\n\n        this.logger.endTimer(\n            `BatchGetCommand [LOCAL_BATCH_GET_VALIDATE]: ${operationId} ${uals.length}`,\n        );\n\n        this.logger.startTimer(`BatchGetCommand [LOCAL]: ${operationId} ${uals.length}`);\n\n        ualPresentLocally.forEach((ual) => {\n            finalResult.local.push(ual);\n            delete tokenIds[ual];\n        });\n\n        if (ualNotPresentLocally.length === 0) {\n            await this.operationService.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                finalResult,\n                [\n                    OPERATION_ID_STATUS.GET.GET_LOCAL_END,\n                    OPERATION_ID_STATUS.GET.GET_END,\n                    OPERATION_ID_STATUS.COMPLETED,\n                ],\n            );\n\n            return Command.empty();\n        }\n\n        this.logger.endTimer(`BatchGetCommand [LOCAL]: ${operationId} ${uals.length}`);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.GET.GET_LOCAL_END,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_FIND_SHARD_START,\n        );\n\n        this.logger.startTimer(`BatchGetCommand [FIND_SHARD]: ${operationId} ${uals.length}`);\n\n        let nodesInfo = [];\n        // if (paranetNodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n        //     const onChainNodes = await this.blockchainModuleManager.getPermissionedNodes(\n        //         blockchain,\n        //         paranetId,\n        //     );\n        //     const foundNodes = await Promise.all(\n        //         onChainNodes.map(async (node) =>\n        //             this.shardingTableService.findPeerAddressAndProtocols(\n        //                 this.cryptoService.convertHexToAscii(node.nodeId),\n        //             ),\n        //         ),\n        //     );\n        //     const networkProtocols = this.operationService.getNetworkProtocols();\n\n        //     for (const node of foundNodes) {\n        //         if (node.id !== currentPeerId) {\n        //             nodesInfo.push({ id: node.id, protocol: networkProtocols[0] });\n        //         }\n        //     }\n        // } else {\n        nodesInfo = await this.findShardNodes(operationId, blockchain, currentPeerId);\n        // Make order of nodes random, shuffle the array\n        nodesInfo = nodesInfo.sort(() => Math.random() - 0.5);\n        // }\n\n        if (nodesInfo.length < 1) {\n            await this.handleError(\n                operationId,\n                blockchain,\n                `Unable to find enough nodes for operationId: ${operationId}. Minimum number of nodes required: 1`,\n                ERROR_TYPE.FIND_SHARD.BATCH_GET_FIND_SHARD_ERROR,\n                true,\n            );\n            return Command.empty();\n        }\n\n        this.logger.endTimer(`BatchGetCommand [FIND_SHARD]: ${operationId} ${uals.length}`);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_FIND_SHARD_END,\n        );\n\n        this.logger.startTimer(`BatchGetCommand [NETWORK]: ${operationId} ${uals.length}`);\n\n        let index = 0;\n        let commandCompleted = false;\n\n        const initialMissing = ualNotPresentLocally.length;\n\n        const hasReachedThreshold = () => {\n            if (initialMissing === 0) {\n                return true;\n            }\n            const retrieved = initialMissing - ualNotPresentLocally.length;\n            const ratio = (retrieved / initialMissing) * 100;\n            return ratio >= this.DONE_THRESHOLD;\n        };\n\n        while (index < nodesInfo.length && ualNotPresentLocally.length > 0 && !commandCompleted) {\n            const batch = nodesInfo.slice(index, index + BATCH_SIZE);\n            const message = {\n                blockchain,\n                tokenIds,\n                includeMetadata,\n                uals: ualNotPresentLocally,\n                repository,\n            };\n\n            // eslint-disable-next-line no-loop-func\n            const messagePromises = batch.map(async (node) => {\n                try {\n                    this.logger.startTimer(\n                        `BatchGetCommand [NETWORK_SEND_MESSAGE]: ${operationId} ${uals.length} ${node.id}`,\n                    );\n                    const result = await this.sendMessage(node, operationId, message);\n                    this.logger.endTimer(\n                        `BatchGetCommand [NETWORK_SEND_MESSAGE]: ${operationId} ${uals.length} ${node.id}`,\n                    );\n\n                    if (commandCompleted || !result.success) {\n                        return;\n                    }\n\n                    this.logger.startTimer(\n                        `BatchGetCommand [NETWORK_VALIDATE_RESPONSE]: ${operationId} ${uals.length} ${node.id}`,\n                    );\n                    const validationResult = await this.validateBatchResponse(\n                        result.responseData.assertions,\n                        blockchain,\n                        paranetNodesAccessPolicy,\n                        contentType,\n                        finalResult,\n                        [OPERATION_ID_STATUS.GET.GET_END, OPERATION_ID_STATUS.COMPLETED],\n                    );\n                    this.logger.endTimer(\n                        `BatchGetCommand [NETWORK_VALIDATE_RESPONSE]: ${operationId} ${uals.length} ${node.id}`,\n                    );\n\n                    if (commandCompleted) {\n                        return;\n                    }\n\n                    for (const [ual, isKCValid] of Object.entries(validationResult)) {\n                        if (isKCValid) {\n                            finalResult.remote[ual] = result.responseData.assertions[ual];\n                            finalResult.metadata[ual] = result.responseData.metadata[ual];\n                            const idx = ualNotPresentLocally.indexOf(ual);\n                            if (idx !== -1) {\n                                ualNotPresentLocally.splice(idx, 1);\n                            }\n                        }\n                    }\n\n                    if (hasReachedThreshold() && !commandCompleted) {\n                        commandCompleted = true;\n                        this.logger.startTimer(\n                            `BatchGetCommand [NETWORK_MARK_AS_COMPLETED]: ${operationId} ${uals.length} ${node.id}`,\n                        );\n                        await this.operationService.markOperationAsCompleted(\n                            operationId,\n                            blockchain,\n                            finalResult,\n                            [OPERATION_ID_STATUS.GET.GET_END, OPERATION_ID_STATUS.COMPLETED],\n                        );\n                        this.logger.endTimer(\n                            `BatchGetCommand [NETWORK_MARK_AS_COMPLETED]: ${operationId} ${uals.length} ${node.id}`,\n                        );\n                    }\n                } catch (err) {\n                    this.logger.warn(`Node ${node.id} failed: ${err.message}`);\n                }\n            });\n\n            // eslint-disable-next-line no-await-in-loop, no-loop-func\n            await new Promise((resolve) => {\n                let settledPromises = 0;\n\n                const countSettledAndMaybeResolve = () => {\n                    settledPromises += 1;\n\n                    const allSettled = settledPromises === messagePromises.length;\n                    if (\n                        commandCompleted ||\n                        allSettled // Safety net to stop infinite hang\n                    ) {\n                        resolve();\n                    }\n                };\n\n                // eslint-disable-next-line no-loop-func\n                messagePromises.forEach((p) => p.finally(countSettledAndMaybeResolve));\n            });\n\n            index += BATCH_SIZE;\n        }\n\n        // Just in case we finish outside early-exit\n        if (!commandCompleted) {\n            await this.operationService.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                finalResult,\n                [OPERATION_ID_STATUS.GET.GET_END, OPERATION_ID_STATUS.COMPLETED],\n            );\n        }\n\n        this.logger.endTimer(`BatchGetCommand [NETWORK]: ${operationId} ${uals.length}`);\n\n        return Command.empty();\n    }\n\n    async validateUALs(operationId, blockchain, uals) {\n        if (uals.length === 0) {\n            return {\n                isValid: false,\n                errorMessage: `Get for operation id: ${operationId}, UALs: ${uals}: no UALs provided.`,\n            };\n        }\n\n        const validationPromises = uals.map(async (ual) => {\n            const isUAL = this.ualService.isUAL(ual);\n            if (!isUAL) {\n                return {\n                    isValid: false,\n                    errorMessage: `Get for operation id: ${operationId}, UAL: ${ual}: is not a UAL.`,\n                };\n            }\n\n            const { contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n\n            const isValidUal = await this.validationService.validateUal(\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n            );\n\n            if (!isValidUal) {\n                return {\n                    isValid: false,\n                    errorMessage: `Get for operation id: ${operationId}, UAL: ${ual}: there is no asset with this UAL.`,\n                };\n            }\n\n            return {\n                isValid: true,\n                errorMessage: null,\n            };\n        });\n\n        const results = await Promise.all(validationPromises);\n\n        // Find the first invalid result if any\n        const invalidResult = results.find((result) => !result.isValid);\n        if (invalidResult) {\n            return invalidResult;\n        }\n\n        return {\n            isValid: true,\n            errorMessage: null,\n        };\n    }\n\n    // async validateParanet(\n    //     operationId,\n    //     paranetUAL,\n    //     paranetBlockchain,\n    //     paranetKnowledgeAssetId,\n    //     paranetNodeAccessPolicy,\n    //     paranetId,\n    //     blockchain,\n    //     uals,\n    // ) {\n    //     if (!paranetKnowledgeAssetId) {\n    //         return {\n    //             isValid: false,\n    //             errorMessage: `Invalid paranet UAL: ${paranetUAL} . Paranet knowledge asset token id is required!`,\n    //         };\n    //     }\n    //     const isParanetUAL = this.ualService.isUAL(paranetUAL);\n\n    //     if (!isParanetUAL) {\n    //         return {\n    //             isValid: false,\n    //             errorMessage: `Get for operation id: ${operationId}, Paranet UAL: ${paranetUAL}: is not a UAL.`,\n    //         };\n    //     }\n\n    //     const [paranetExists, chainParanetNodesAccessPolicy] = await Promise.all([\n    //         this.blockchainModuleManager.paranetExists(paranetBlockchain, paranetId),\n    //         this.blockchainModuleManager.getNodesAccessPolicy(paranetBlockchain, paranetId),\n    //     ]);\n\n    //     if (!paranetExists) {\n    //         return {\n    //             isValid: false,\n    //             errorMessage: `Get for operation id: ${operationId}, Paranet UAL: ${paranetUAL}: paranet does not exist.`,\n    //         };\n    //     }\n\n    //     if (paranetNodeAccessPolicy !== chainParanetNodesAccessPolicy) {\n    //         return {\n    //             isValid: false,\n    //             errorMessage: `Get for operation id: ${operationId}, Paranet UAL: ${paranetUAL}: onchain paranet access policy does not match the requested paranet access policy.`,\n    //         };\n    //     }\n\n    //     const validationPromises = uals.map(async (ual) => {\n    //         const { contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n    //         const knowledgeCollectionOnchainId = this.cryptoService.keccak256EncodePacked(\n    //             ['address', 'uint256'],\n    //             [contract, knowledgeCollectionId],\n    //         );\n    //         const paranetContainsKnowledgeCollection =\n    //             await this.blockchainModuleManager.isKnowledgeCollectionRegistered(\n    //                 blockchain,\n    //                 paranetId,\n    //                 knowledgeCollectionOnchainId,\n    //             );\n    //         if (!paranetContainsKnowledgeCollection) {\n    //             return {\n    //                 isValid: false,\n    //                 errorMessage: `Paranet UAL: ${paranetUAL} does not contain Knowledge Collection: ${ual}`,\n    //             };\n    //         }\n    //         return {\n    //             isValid: true,\n    //             errorMessage: null,\n    //         };\n    //     });\n\n    //     const results = await Promise.all(validationPromises);\n\n    //     // Find the first invalid result if any\n    //     const invalidResult = results.find((result) => !result.isValid);\n    //     if (invalidResult) {\n    //         return invalidResult;\n    //     }\n\n    //     return {\n    //         isValid: true,\n    //         errorMessage: null,\n    //     };\n    // }\n\n    async findShardNodes(operationId, blockchain, currentPeerId) {\n        this.logger.debug(`Searching for shard for operationId: ${operationId}`);\n\n        const networkProtocols = this.operationService.getNetworkProtocols();\n\n        const shardNodes = await this.shardingTableService.findShard(blockchain, true);\n\n        // TODO: Optimize this so it's returned by shardingTableService.findShard\n        const foundNodes = await Promise.all(\n            shardNodes.map(({ peerId }) =>\n                this.shardingTableService.findPeerAddressAndProtocols(peerId),\n            ),\n        );\n        const nodesInfo = [];\n        for (const node of foundNodes) {\n            if (node.id !== currentPeerId) {\n                nodesInfo.push({ id: node.id, protocol: networkProtocols[0] });\n            }\n        }\n\n        this.logger.debug(`Found ${nodesInfo.length} node(s) for operationId: ${operationId}`);\n        this.logger.trace(\n            `Found shard: ${JSON.stringify(\n                nodesInfo.map((node) => node.id),\n                null,\n                2,\n            )}`,\n        );\n        return nodesInfo;\n    }\n\n    async sendMessage(node, operationId, message) {\n        const response = await this.messagingService.sendProtocolMessage(\n            node,\n            operationId,\n            message,\n            NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST,\n            NETWORK_MESSAGE_TIMEOUT_MILLS.BATCH_GET.REQUEST,\n        );\n        return {\n            success: response.header.messageType === NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n            responseData: response.data,\n        };\n    }\n\n    async validateBatchResponse(\n        responseData,\n        blockchain,\n        paranetNodesAccessPolicy,\n        contentType,\n        finalResult,\n    ) {\n        const validationResults = {};\n        await Promise.all(\n            Object.entries(responseData).map(async ([ual, assertion]) => {\n                // Already received and validate this assertion\n                if (finalResult.remote[ual]) {\n                    return;\n                }\n                if (contentType === 'private') {\n                    validationResults[ual] = true;\n                    return;\n                }\n                const filteredPublic = [];\n                const privateHashTriples = [];\n\n                // Separate public vs private hash triples\n                if (!assertion.public || assertion.public.length === 0) {\n                    validationResults[ual] = false;\n                    return;\n                }\n                assertion.public.forEach((triple) => {\n                    if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                        privateHashTriples.push(triple);\n                    } else {\n                        filteredPublic.push(triple);\n                    }\n                });\n\n                // Group triples by subject\n                const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n                    filteredPublic,\n                    true,\n                );\n                publicKnowledgeAssetsTriplesGrouped.push(\n                    ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n                );\n\n                try {\n                    // Validate public dataset\n                    const { contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n                    await this.validationService.validateDatasetOnBlockchain(\n                        publicKnowledgeAssetsTriplesGrouped.map((t) => t.sort()).flat(),\n                        blockchain,\n                        contract,\n                        knowledgeCollectionId,\n                    );\n\n                    // If not permissioned and there are private triples, validate\n                    if (assertion?.private?.length) {\n                        await this.validationService.validatePrivateMerkleRoot(\n                            assertion.public,\n                            assertion.private,\n                        );\n                    }\n                    validationResults[ual] = true;\n                } catch (e) {\n                    this.logger.error(`Validation failed for UAL ${ual}: ${e.name}, ${e.message}`);\n                    validationResults[ual] = false;\n                }\n            }),\n        );\n\n        return validationResults;\n    }\n\n    /**\n     * Builds default GetCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'batchGetCommand',\n            delay: 0,\n            transactional: false,\n            priority: COMMAND_PRIORITY.MEDIUM,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default BatchGetCommand;\n"
  },
  {
    "path": "src/commands/protocols/get/sender/get-command.js",
    "content": "import { kcTools } from 'assertion-tools';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport Command from '../../../command.js';\nimport {\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    PARANET_ACCESS_POLICY,\n    TRIPLE_STORE_REPOSITORIES,\n    NETWORK_MESSAGE_TYPES,\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    PRIVATE_ASSERTION_PREDICATE,\n    PRIVATE_HASH_SUBJECT_PREFIX,\n    MIGRATION_FLAG_PATH,\n    COMMAND_PRIORITY,\n} from '../../../../constants/constants.js';\n\nclass GetCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n        this.ualService = ctx.ualService;\n        this.operationService = ctx.getService;\n        this.validationService = ctx.validationService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.paranetService = ctx.paranetService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.shardingTableService = ctx.shardingTableService;\n        this.cryptoService = ctx.cryptoService;\n        this.messagingService = ctx.messagingService;\n        this.tripleStoreModuleManager = ctx.tripleStoreModuleManager;\n        this.pendingStorageService = ctx.pendingStorageService;\n    }\n\n    async handleError(operationId, blockchain, errorMessage, errorType) {\n        await this.operationService.markOperationAsFailed(\n            operationId,\n            blockchain,\n            errorMessage,\n            errorType,\n        );\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.GET.GET_FAILED,\n            operationId,\n            blockchain,\n        );\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const {\n            operationId,\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            ual,\n            paranetUAL,\n            paranetSync,\n            contentType,\n            includeMetadata,\n            paranetNodesAccessPolicy,\n            knowledgeAssetId,\n            minimumNumberOfNodeReplications,\n        } = command.data;\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.GET.GET_VALIDATE_ASSET_START,\n        );\n\n        const maxGetRetries = 3;\n        const getRetryDelayMs = 5_000;\n        let ualValidationPassed = false;\n        let ualValidationError = null;\n\n        for (let attempt = 1; attempt <= maxGetRetries; attempt += 1) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                const { isValid, errorMessage: valMsg } = await this.validateUAL(\n                    operationId,\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                    ual,\n                );\n                if (isValid) {\n                    ualValidationPassed = true;\n                    break;\n                }\n                ualValidationError = valMsg;\n            } catch (err) {\n                ualValidationError = `UAL validation failed: ${err.message}`;\n            }\n\n            if (!ualValidationPassed) {\n                try {\n                    // eslint-disable-next-line no-await-in-loop\n                    const cachedResult = await this._tryCacheFallback(\n                        blockchain,\n                        contract,\n                        knowledgeCollectionId,\n                        knowledgeAssetId,\n                        ual,\n                        operationId,\n                        paranetNodesAccessPolicy,\n                        contentType,\n                    );\n                    if (cachedResult) {\n                        return cachedResult;\n                    }\n                } catch (_cacheErr) {\n                    // cache fallback also failed, will retry\n                }\n            }\n\n            if (attempt < maxGetRetries) {\n                this.logger.debug(\n                    `Get validation/cache attempt ${attempt}/${maxGetRetries} failed for ${ual}, retrying in ${getRetryDelayMs}ms`,\n                );\n                // eslint-disable-next-line no-await-in-loop\n                await new Promise((resolve) => {\n                    setTimeout(resolve, getRetryDelayMs);\n                });\n            }\n        }\n\n        if (!ualValidationPassed) {\n            await this.handleError(\n                operationId,\n                blockchain,\n                ualValidationError,\n                ERROR_TYPE.GET.GET_VALIDATE_ASSET_ERROR,\n            );\n            return Command.empty();\n        }\n\n        const currentPeerId = this.networkModuleManager.getPeerId().toB58String();\n        let paranetId;\n        let repository = TRIPLE_STORE_REPOSITORIES.DKG;\n        let migrationFlag = '0';\n        const migrationFlagPath = path.join(process.cwd(), MIGRATION_FLAG_PATH);\n        try {\n            migrationFlag = await fs.readFile(migrationFlagPath, 'utf8');\n            migrationFlag = migrationFlag.trim();\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                this.logger.warn(\n                    `Migration flag file not found at ${migrationFlagPath}, using default value '${migrationFlag}'`,\n                );\n            } else {\n                throw error;\n            }\n        }\n        if (paranetUAL) {\n            const {\n                blockchain: paranetBlockchain,\n                contract: paranetContract,\n                knowledgeCollectionId: paranetKnowledgeCollectionId,\n                knowledgeAssetId: paranetKnowledgeAssetId,\n            } = this.ualService.resolveUAL(paranetUAL);\n            paranetId = this.paranetService.constructParanetId(\n                paranetContract,\n                paranetKnowledgeCollectionId,\n                paranetKnowledgeAssetId,\n            );\n\n            if (!paranetSync && migrationFlag === '0') {\n                // query the paranet repository if the migration is not yet finished\n                repository = this.paranetService.getParanetRepositoryName(paranetUAL);\n                const repositoryExists =\n                    this.tripleStoreModuleManager.repositoryInitilized(repository);\n\n                if (!repositoryExists) {\n                    repository = TRIPLE_STORE_REPOSITORIES.DKG;\n                }\n            }\n\n            const { isValid: paranetIsValid, errorMessage: paranetErrorMessage } =\n                await this.validateParanet(\n                    operationId,\n                    paranetUAL,\n                    paranetBlockchain,\n                    paranetKnowledgeAssetId,\n                    paranetNodesAccessPolicy,\n                    paranetId,\n                    knowledgeCollectionId,\n                    blockchain,\n                    contract,\n                    ual,\n                );\n            if (!paranetIsValid) {\n                await this.handleError(\n                    operationId,\n                    blockchain,\n                    paranetErrorMessage,\n                    ERROR_TYPE.GET.GET_VALIDATE_ASSET_ERROR,\n                );\n                return Command.empty();\n            }\n        }\n\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.GET.GET_VALIDATE_ASSET_END,\n            operationId,\n            blockchain,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.GET.GET_LOCAL_START,\n        );\n        let tokenIds;\n        if (!knowledgeAssetId) {\n            try {\n                tokenIds = await this.blockchainModuleManager.getKnowledgeAssetsRange(\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                );\n            } catch (error) {\n                // Asset created on old content asset storage contract\n                tokenIds = {\n                    startTokenId: 1,\n                    endTokenId: 1,\n                    burned: [],\n                };\n            }\n        } else {\n            // kaId is number, so transform it to range\n            tokenIds = {\n                startTokenId: knowledgeAssetId,\n                endTokenId: knowledgeAssetId,\n                burned: [],\n            };\n        }\n\n        const promises = [];\n        const assertionPromise = this.tripleStoreService.getAssertion(\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            knowledgeAssetId,\n            tokenIds,\n            migrationFlag,\n            contentType,\n            repository,\n        );\n        promises.push(assertionPromise);\n\n        if (includeMetadata) {\n            const metadataPromise = this.tripleStoreService.getAssertionMetadata(\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n                repository,\n            );\n            promises.push(metadataPromise);\n        }\n\n        const [assertion, metadata] = await Promise.all(promises);\n\n        const responseData = {\n            assertion,\n            ...(includeMetadata && metadata && { metadata }),\n        };\n        let localGetPassed = true;\n        if (paranetNodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n            if (Array.isArray(assertion?.public)) {\n                const assertionShouldHavePrivateTriples = assertion?.public?.some((triple) =>\n                    triple.includes(`${PRIVATE_ASSERTION_PREDICATE}`),\n                );\n                if (assertionShouldHavePrivateTriples) {\n                    localGetPassed = assertion?.private?.length > 0;\n                }\n            } else {\n                localGetPassed = false;\n            }\n        }\n        const localGetResultValid = await this.validateResponse(\n            { assertion },\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            knowledgeAssetId,\n            paranetNodesAccessPolicy,\n            contentType,\n        );\n        if (\n            localGetPassed &&\n            localGetResultValid &&\n            (assertion?.public?.length || assertion?.private?.length || assertion?.length)\n        ) {\n            await this.operationService.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                responseData,\n                [\n                    OPERATION_ID_STATUS.GET.GET_LOCAL_END,\n                    OPERATION_ID_STATUS.GET.GET_END,\n                    OPERATION_ID_STATUS.COMPLETED,\n                ],\n            );\n\n            return Command.empty();\n        }\n        this.logger.debug(`Could not find asset with UAL: ${ual} locally`);\n\n        try {\n            const cachedResult = await this._tryCacheFallback(\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n                knowledgeAssetId,\n                ual,\n                operationId,\n                paranetNodesAccessPolicy,\n                contentType,\n            );\n            if (cachedResult) {\n                return cachedResult;\n            }\n        } catch (cacheErr) {\n            this.logger.debug(\n                `Pending storage cache fallback failed for ${ual}: ${cacheErr.message}`,\n            );\n        }\n\n        await this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.GET.GET_LOCAL_END,\n            operationId,\n            blockchain,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.GET.GET_SHARD_START,\n        );\n\n        let nodesInfo = [];\n        if (paranetNodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n            const onChainNodes = await this.blockchainModuleManager.getPermissionedNodes(\n                blockchain,\n                paranetId,\n            );\n            const foundNodes = await Promise.all(\n                onChainNodes.map(async (node) =>\n                    this.shardingTableService.findPeerAddressAndProtocols(\n                        this.cryptoService.convertHexToAscii(node.nodeId),\n                    ),\n                ),\n            );\n            const networkProtocols = this.operationService.getNetworkProtocols();\n\n            for (const node of foundNodes) {\n                if (node.id !== currentPeerId) {\n                    nodesInfo.push({ id: node.id, protocol: networkProtocols[0] });\n                }\n            }\n        } else {\n            nodesInfo = await this.findShardNodes(operationId, blockchain, currentPeerId);\n        }\n\n        this.minAckResponses = this.operationService.getMinAckResponses(\n            minimumNumberOfNodeReplications,\n        );\n\n        if (nodesInfo.length < this.minAckResponses) {\n            await this.handleError(\n                operationId,\n                blockchain,\n                `Unable to find enough nodes for operationId: ${operationId}. Minimum number of nodes required: ${this.minAckResponses}`,\n                ERROR_TYPE.FIND_SHARD.GET_FIND_SHARD_ERROR,\n                true,\n            );\n            return Command.empty();\n        }\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.GET.GET_SHARD_END,\n        );\n\n        const message = {\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            knowledgeAssetId,\n            tokenIds,\n            includeMetadata,\n            ual,\n            paranetUAL,\n            migrationFlag,\n            repository,\n        };\n        const BATCH_SIZE = 5;\n        let index = 0;\n\n        // Process shard nodes in batches\n        while (index < nodesInfo.length) {\n            // Slice out a batch of nodes\n            const batch = nodesInfo.slice(index, index + BATCH_SIZE);\n\n            // Send messages in parallel to all nodes in the current batch\n            // eslint-disable-next-line no-await-in-loop\n            const results = await Promise.all(\n                batch.map((node) => this.sendMessage(node, operationId, message)),\n            );\n\n            const succsesfulResult = [];\n            const failedResults = [];\n\n            results.forEach((result) => {\n                if (result.success) {\n                    succsesfulResult.push(result);\n                } else {\n                    failedResults.push(result);\n                }\n            });\n\n            for (const result of succsesfulResult) {\n                // eslint-disable-next-line no-await-in-loop\n                const isResponseValid = await this.validateResponse(\n                    result.responseData,\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                    knowledgeAssetId,\n                    paranetNodesAccessPolicy,\n                    contentType,\n                );\n                if (isResponseValid) {\n                    this.operationService.markOperationAsCompleted(\n                        operationId,\n                        blockchain,\n                        result.responseData,\n                        [OPERATION_ID_STATUS.GET.GET_END, OPERATION_ID_STATUS.COMPLETED],\n                    );\n                    return Command.empty();\n                }\n            }\n            // Otherwise, continue with the next batch\n            index += BATCH_SIZE;\n        }\n\n        await this.handleError(\n            operationId,\n            blockchain,\n            `No node responded successfully for GET for ${ual}. Minimum required responses: ${this.minAckResponses}. Operation id: ${operationId}`,\n            ERROR_TYPE.FIND_SHARD.GET_ERROR,\n        );\n\n        return Command.empty();\n    }\n\n    async _tryCacheFallback(\n        blockchain,\n        contract,\n        knowledgeCollectionId,\n        knowledgeAssetId,\n        ual,\n        operationId,\n        paranetNodesAccessPolicy,\n        contentType,\n    ) {\n        if (knowledgeAssetId) return null;\n\n        const latestMerkleRoot =\n            await this.blockchainModuleManager.getKnowledgeCollectionLatestMerkleRoot(\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n            );\n        if (!latestMerkleRoot) return null;\n\n        const publishOpId = this.pendingStorageService.getOperationIdByMerkleRoot(latestMerkleRoot);\n        if (!publishOpId) return null;\n\n        const cachedAssertion = await this.pendingStorageService.getCachedDataset(publishOpId);\n        if (\n            !cachedAssertion ||\n            (!cachedAssertion.public?.length && !cachedAssertion.private?.length)\n        ) {\n            return null;\n        }\n\n        const filteredAssertion = this._filterAssertionByContentType(cachedAssertion, contentType);\n        if (!filteredAssertion.public?.length && !filteredAssertion.private?.length) {\n            return null;\n        }\n\n        let cachePassed = true;\n        if (paranetNodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n            if (Array.isArray(filteredAssertion.public)) {\n                const shouldHavePrivate = filteredAssertion.public.some((triple) =>\n                    triple.includes(`${PRIVATE_ASSERTION_PREDICATE}`),\n                );\n                if (shouldHavePrivate) {\n                    cachePassed = filteredAssertion.private?.length > 0;\n                }\n            } else {\n                cachePassed = false;\n            }\n        }\n\n        if (!cachePassed) return null;\n\n        const cachedResponseData = { assertion: filteredAssertion };\n        const isValid = await this.validateResponse(\n            cachedResponseData,\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n            knowledgeAssetId,\n            paranetNodesAccessPolicy,\n            contentType,\n        );\n        if (!isValid) return null;\n\n        this.logger.info(\n            `Serving asset ${ual} from pending storage cache (merkleRoot: ${latestMerkleRoot})`,\n        );\n        await this.operationService.markOperationAsCompleted(\n            operationId,\n            blockchain,\n            cachedResponseData,\n            [\n                OPERATION_ID_STATUS.GET.GET_LOCAL_END,\n                OPERATION_ID_STATUS.GET.GET_END,\n                OPERATION_ID_STATUS.COMPLETED,\n            ],\n        );\n        return Command.empty();\n    }\n\n    _filterAssertionByContentType(assertion, contentType) {\n        if (!contentType || contentType === 'all') return assertion;\n        if (contentType === 'public') {\n            return { public: assertion.public || [] };\n        }\n        if (contentType === 'private') {\n            return { private: assertion.private || [] };\n        }\n        return assertion;\n    }\n\n    async validateUAL(operationId, blockchain, contract, knowledgeCollectionId, ual) {\n        const isUAL = this.ualService.isUAL(ual);\n\n        if (!isUAL) {\n            return {\n                isValid: false,\n                errorMessage: `Get for operation id: ${operationId}, UAL: ${ual}: is not a UAL.`,\n            };\n        }\n\n        const isValidUal = await this.validationService.validateUal(\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n        );\n\n        if (!isValidUal) {\n            return {\n                isValid: false,\n                errorMessage: `Get for operation id: ${operationId}, UAL: ${ual}: there is no asset with this UAL.`,\n            };\n        }\n        return {\n            isValid: true,\n            errorMessage: null,\n        };\n    }\n\n    async validateParanet(\n        operationId,\n        paranetUAL,\n        paranetBlockchain,\n        paranetKnowledgeAssetId,\n        paranetNodeAccessPolicy,\n        paranetId,\n        knowledgeCollectionId,\n        blockchain,\n        contract,\n        ual,\n    ) {\n        if (!paranetKnowledgeAssetId) {\n            return {\n                isValid: false,\n                errorMessage: `Invalid paranet UAL: ${paranetUAL} . Paranet knowledge asset token id is required!`,\n            };\n        }\n        const isParanetUAL = this.ualService.isUAL(paranetUAL);\n\n        if (!isParanetUAL) {\n            return {\n                isValid: false,\n                errorMessage: `Get for operation id: ${operationId}, Paranet UAL: ${paranetUAL}: is not a UAL.`,\n            };\n        }\n\n        const [paranetExists, chainParanetNodesAccessPolicy] = await Promise.all([\n            this.blockchainModuleManager.paranetExists(paranetBlockchain, paranetId),\n            this.blockchainModuleManager.getNodesAccessPolicy(paranetBlockchain, paranetId),\n        ]);\n\n        const knowledgeCollectionOnchainId = this.cryptoService.keccak256EncodePacked(\n            ['address', 'uint256'],\n            [contract, knowledgeCollectionId],\n        );\n        const paranetContainsKnowledgeCollection =\n            await this.blockchainModuleManager.isKnowledgeCollectionRegistered(\n                blockchain,\n                paranetId,\n                knowledgeCollectionOnchainId,\n            );\n        if (!paranetContainsKnowledgeCollection) {\n            return {\n                isValid: false,\n                errorMessage: `Paranet UAL: ${paranetUAL} does not contain Knowledge Collection: ${ual}`,\n            };\n        }\n\n        if (!paranetExists) {\n            return {\n                isValid: false,\n                errorMessage: `Get for operation id: ${operationId}, Paranet UAL: ${paranetUAL}: paranet does not exist.`,\n            };\n        }\n\n        if (paranetNodeAccessPolicy !== chainParanetNodesAccessPolicy) {\n            return {\n                isValid: false,\n                errorMessage: `Get for operation id: ${operationId}, Paranet UAL: ${paranetUAL}: onchain paranet access policy does not match the requested paranet access policy.`,\n            };\n        }\n\n        return {\n            isValid: true,\n            errorMessage: null,\n        };\n    }\n\n    async findShardNodes(operationId, blockchain, currentPeerId) {\n        this.logger.debug(`Searching for shard for operationId: ${operationId}`);\n\n        const networkProtocols = this.operationService.getNetworkProtocols();\n\n        const shardNodes = await this.shardingTableService.findShard(blockchain, true);\n\n        // TODO: Optimize this so it's returned by shardingTableService.findShard\n        const foundNodes = await Promise.all(\n            shardNodes.map(({ peerId }) =>\n                this.shardingTableService.findPeerAddressAndProtocols(peerId),\n            ),\n        );\n        const nodesInfo = [];\n        for (const node of foundNodes) {\n            if (node.id !== currentPeerId) {\n                nodesInfo.push({ id: node.id, protocol: networkProtocols[0] });\n            }\n        }\n\n        this.logger.debug(`Found ${nodesInfo.length} node(s) for operationId: ${operationId}`);\n        this.logger.trace(\n            `Found shard: ${JSON.stringify(\n                nodesInfo.map((node) => node.id),\n                null,\n                2,\n            )}`,\n        );\n        return nodesInfo;\n    }\n\n    async sendMessage(node, operationId, message) {\n        const response = await this.messagingService.sendProtocolMessage(\n            node,\n            operationId,\n            message,\n            NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST,\n            NETWORK_MESSAGE_TIMEOUT_MILLS.GET.REQUEST,\n        );\n        return {\n            success: response.header.messageType === NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n            responseData: response.data,\n        };\n    }\n\n    async validateResponse(\n        responseData,\n        blockchain,\n        contract,\n        knowledgeCollectionId,\n        knowledgeAssetId,\n        paranetNodesAccessPolicy,\n        contentType,\n    ) {\n        if (knowledgeAssetId) {\n            return true;\n        }\n        if (responseData?.assertion?.public) {\n            // We can only validate whole collection not particular KA\n            if (\n                !knowledgeAssetId ||\n                (typeof knowledgeAssetId === 'object' &&\n                    Object.keys(knowledgeAssetId).length === 3 &&\n                    'startTokenId' in knowledgeAssetId &&\n                    'endTokenId' in knowledgeAssetId &&\n                    'burned' in knowledgeAssetId &&\n                    Array.isArray(knowledgeAssetId.burned))\n            ) {\n                const publicAssertion = responseData?.assertion?.public;\n\n                const filteredPublic = [];\n                const privateHashTriples = [];\n                publicAssertion.forEach((triple) => {\n                    if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                        privateHashTriples.push(triple);\n                    } else {\n                        filteredPublic.push(triple);\n                    }\n                });\n\n                const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n                    filteredPublic,\n                    true,\n                );\n                publicKnowledgeAssetsTriplesGrouped.push(\n                    ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n                );\n\n                try {\n                    await this.validationService.validateDatasetOnBlockchain(\n                        publicKnowledgeAssetsTriplesGrouped.map((t) => t.sort()).flat(),\n                        blockchain,\n                        contract,\n                        knowledgeCollectionId,\n                    );\n\n                    if (paranetNodesAccessPolicy === PARANET_ACCESS_POLICY.PERMISSIONED) {\n                        if (Array.isArray(responseData?.assertion?.public)) {\n                            const assertionShouldHavePrivateTriples =\n                                responseData?.assertion?.public?.some((triple) =>\n                                    triple.includes(`${PRIVATE_ASSERTION_PREDICATE}`),\n                                );\n                            if (assertionShouldHavePrivateTriples) {\n                                if (responseData?.assertion?.private?.length > 0) {\n                                    await this.validationService.validatePrivateMerkleRoot(\n                                        responseData.assertion.public,\n                                        responseData.assertion.private,\n                                    );\n                                    return true;\n                                }\n                            }\n                        }\n                        return false;\n                    }\n\n                    if (responseData.assertion?.private?.length) {\n                        await this.validationService.validatePrivateMerkleRoot(\n                            responseData.assertion.public,\n                            responseData.assertion.private,\n                        );\n                        return true;\n                    }\n                } catch (e) {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n        if (\n            !responseData?.assertion?.public &&\n            responseData?.assertion?.private &&\n            contentType === 'private'\n        ) {\n            // if there is only private part skip validation\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * Builds default GetCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'getCommand',\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGH,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default GetCommand;\n"
  },
  {
    "path": "src/commands/protocols/publish/publish-finalization-command.js",
    "content": "import Command from '../../command.js';\nimport {\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    MAX_RETRIES_READ_CACHED_PUBLISH_DATA,\n    RETRY_DELAY_READ_CACHED_PUBLISH_DATA,\n    TRIPLE_STORE_REPOSITORIES,\n    NETWORK_MESSAGE_TYPES,\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    COMMAND_PRIORITY,\n} from '../../../constants/constants.js';\n\nclass PublishFinalizationCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.ualService = ctx.ualService;\n        this.fileService = ctx.fileService;\n        this.messagingService = ctx.messagingService;\n        this.operationService = ctx.finalityService;\n        this.errorType = ERROR_TYPE.STORE_ASSERTION_ERROR;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.operationIdService = ctx.operationIdService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.dataService = ctx.dataService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async execute(command) {\n        const { event } = command.data;\n        const eventData = JSON.parse(event.data);\n        const { txHash, blockNumber } = event;\n        const { id, publishOperationId, merkleRoot, byteSize } = eventData;\n        const { blockchain, contractAddress } = event;\n        const operationId = this.operationIdService.generateId();\n        const ual = this.ualService.deriveUAL(blockchain, contractAddress, id);\n\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_START,\n            operationId,\n            blockchain,\n            publishOperationId,\n        );\n        let transaction;\n        let blockTimestamp;\n        try {\n            [transaction, blockTimestamp] = await Promise.all([\n                this.blockchainModuleManager.getTransaction(blockchain, txHash),\n                this.blockchainModuleManager.getBlockTimestamp(blockchain, blockNumber),\n            ]);\n        } catch (error) {\n            this.logger.error(`Failed to get transaction or block timestamp: ${error.message}`);\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.FAILED,\n                operationId,\n                blockchain,\n                publishOperationId,\n            );\n            return Command.empty();\n        }\n        const metadata = {\n            publisherKey: transaction.from.toLowerCase(),\n            blockNumber,\n            txHash,\n            blockTimestamp,\n        };\n        let publisherPeerId;\n        let cachedMerkleRoot;\n        let assertion;\n        try {\n            const result = await this.readWithRetries(publishOperationId);\n            cachedMerkleRoot = result.merkleRoot;\n            assertion = result.assertion;\n            publisherPeerId = result.remotePeerId;\n        } catch (_error) {\n            this.logger.warn(\n                `[Cache] Failed to read cached publish data for UAL ${ual} (publishOperationId: ${publishOperationId}, txHash: ${txHash}, operationId: ${operationId})`,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.FAILED,\n                operationId,\n                blockchain,\n                publishOperationId,\n            );\n            return Command.empty();\n        }\n\n        try {\n            await this.validatePublishData(merkleRoot, cachedMerkleRoot, byteSize, assertion, ual);\n        } catch (e) {\n            this.logger.error(`Failed to validate publish data: ${e.message}`);\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.FAILED,\n                operationId,\n                blockchain,\n                publishOperationId,\n            );\n            return Command.empty();\n        }\n\n        try {\n            await this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_STORE_ASSERTION_START,\n                operationId,\n                blockchain,\n            );\n\n            const totalTriples = await this.tripleStoreService.insertKnowledgeCollection(\n                TRIPLE_STORE_REPOSITORIES.DKG,\n                ual,\n                assertion,\n                metadata,\n            );\n\n            await this.repositoryModuleManager.incrementInsertedTriples(totalTriples ?? 0);\n            this.logger.info(`Number of triples added to the database +${totalTriples}`);\n\n            await this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_STORE_ASSERTION_END,\n                operationId,\n                blockchain,\n            );\n\n            const myPeerId = this.networkModuleManager.getPeerId().toB58String();\n            if (publisherPeerId === myPeerId) {\n                await this.repositoryModuleManager.saveFinalityAck(\n                    publishOperationId,\n                    ual,\n                    publisherPeerId,\n                );\n\n                for (const status of this.operationService.completedStatuses) {\n                    this.operationIdService.emitChangeEvent(status, operationId, blockchain);\n                }\n            } else {\n                const networkProtocols = this.operationService.getNetworkProtocols();\n                const node = { id: publisherPeerId, protocol: networkProtocols[0] };\n\n                const message = { ual, publishOperationId, blockchain, operationId };\n\n                const maxFinalityAttempts = 3;\n                const backoffDelays = [0, 5_000, 10_000];\n                let response;\n                let lastError;\n\n                for (let attempt = 0; attempt < maxFinalityAttempts; attempt += 1) {\n                    if (backoffDelays[attempt] > 0) {\n                        // eslint-disable-next-line no-await-in-loop\n                        await new Promise((r) => {\n                            setTimeout(r, backoffDelays[attempt]);\n                        });\n                    }\n                    try {\n                        // eslint-disable-next-line no-await-in-loop\n                        response = await this.messagingService.sendProtocolMessage(\n                            node,\n                            operationId,\n                            message,\n                            NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST,\n                            NETWORK_MESSAGE_TIMEOUT_MILLS.FINALITY.REQUEST,\n                        );\n                        lastError = null;\n                        break;\n                    } catch (err) {\n                        lastError = err;\n                        this.logger.warn(\n                            `Finality request to publisher ${publisherPeerId} failed ` +\n                                `(attempt ${attempt + 1}/${maxFinalityAttempts}): ${err.message}`,\n                        );\n                    }\n                }\n\n                if (lastError) {\n                    throw lastError;\n                }\n\n                await this.messagingService.handleProtocolResponse(\n                    response,\n                    this.operationService,\n                    blockchain,\n                    operationId,\n                );\n            }\n        } catch (e) {\n            this.logger.error(`Command error (${this.errorType}): ${e.message}`);\n\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.FAILED,\n                operationId,\n                blockchain,\n                publishOperationId,\n            );\n        }\n\n        return Command.empty();\n    }\n\n    async validatePublishData(merkleRoot, cachedMerkleRoot, byteSize, assertion, ual) {\n        if (merkleRoot !== cachedMerkleRoot) {\n            const errorMessage = `Invalid Merkle Root for Knowledge Collection: ${ual}. Received value from blockchain: ${merkleRoot}, Cached value from publish operation: ${cachedMerkleRoot}`;\n\n            throw new Error(errorMessage);\n        }\n\n        const calculatedAssertionSize = this.dataService.calculateAssertionSize(\n            assertion.public ?? assertion,\n        );\n\n        if (byteSize.toString() !== calculatedAssertionSize.toString()) {\n            const errorMessage = `Invalid Assertion Size for Knowledge Collection: ${ual}. Received value from blockchain: ${byteSize}, Calculated value: ${calculatedAssertionSize}`;\n\n            throw new Error(errorMessage);\n        }\n    }\n\n    async readWithRetries(publishOperationId) {\n        let attempt = 0;\n        const datasetPath = this.fileService.getPendingStorageDocumentPath(publishOperationId);\n\n        while (attempt < MAX_RETRIES_READ_CACHED_PUBLISH_DATA) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                const cachedData = await this.fileService.readFile(datasetPath, true);\n                return cachedData;\n            } catch (error) {\n                attempt += 1;\n                if (attempt < MAX_RETRIES_READ_CACHED_PUBLISH_DATA) {\n                    this.logger.debug(\n                        `[Cache] Read attempt ${attempt}/${MAX_RETRIES_READ_CACHED_PUBLISH_DATA} ` +\n                            `failed for publishOperationId: ${publishOperationId}, retrying in ${RETRY_DELAY_READ_CACHED_PUBLISH_DATA}ms...`,\n                    );\n                    // eslint-disable-next-line no-await-in-loop\n                    await new Promise((resolve) => {\n                        setTimeout(resolve, RETRY_DELAY_READ_CACHED_PUBLISH_DATA);\n                    });\n                }\n            }\n        }\n        this.logger.warn(\n            `[Cache] Exhausted retries reading cached publish data (publishOperationId: ${publishOperationId}, path: ${datasetPath}).`,\n        );\n        throw new Error('Failed to read cached publish data');\n    }\n\n    /**\n     * Builds default readCachedPublishDataCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'publishFinalizationCommand',\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PublishFinalizationCommand;\n"
  },
  {
    "path": "src/commands/protocols/publish/receiver/v1.0.0/v1-0-0-handle-store-request-command.js",
    "content": "import HandleProtocolMessageCommand from '../../../common/handle-protocol-message-command.js';\n\nimport {\n    NETWORK_MESSAGE_TYPES,\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    COMMAND_PRIORITY,\n} from '../../../../../constants/constants.js';\n\nclass HandleStoreRequestCommand extends HandleProtocolMessageCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.validationService = ctx.validationService;\n        this.operationService = ctx.publishService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.ualService = ctx.ualService;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.operationIdService = ctx.operationIdService;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.signatureService = ctx.signatureService;\n\n        this.errorType = ERROR_TYPE.PUBLISH.PUBLISH_LOCAL_STORE_REMOTE_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.PUBLISH.PUBLISH_LOCAL_STORE_REMOTE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.PUBLISH.PUBLISH_LOCAL_STORE_REMOTE_END;\n    }\n\n    async prepareMessage(commandData) {\n        const { blockchain, operationId, datasetRoot, remotePeerId, isOperationV0 } = commandData;\n\n        await this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_VALIDATE_ASSET_REMOTE_START,\n            operationId,\n            blockchain,\n        );\n\n        const { dataset } = await this.operationIdService.getCachedOperationIdData(operationId);\n\n        const validationResult = await this.validateReceivedData(\n            operationId,\n            datasetRoot,\n            dataset,\n            blockchain,\n            isOperationV0,\n        );\n\n        await this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_VALIDATE_ASSET_REMOTE_END,\n            operationId,\n            blockchain,\n        );\n\n        if (validationResult.messageType === NETWORK_MESSAGE_TYPES.RESPONSES.NACK) {\n            return validationResult;\n        }\n\n        await this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_LOCAL_STORE_REMOTE_CACHE_DATASET_START,\n            operationId,\n            blockchain,\n        );\n        if (isOperationV0) {\n            const { contract, tokenId } = commandData;\n            const ual = this.ualService.deriveUAL(blockchain, contract, tokenId);\n            await this.tripleStoreService.createV6KnowledgeCollection(dataset, ual);\n        } else {\n            await this.pendingStorageService.cacheDataset(\n                operationId,\n                datasetRoot,\n                dataset,\n                remotePeerId,\n            );\n        }\n        await this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_LOCAL_STORE_REMOTE_CACHE_DATASET_END,\n            operationId,\n            blockchain,\n        );\n\n        const identityId = await this.blockchainModuleManager.getIdentityId(blockchain);\n\n        const { v, r, s, vs } = await this.signatureService.signMessage(blockchain, datasetRoot);\n\n        await this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_VALIDATE_ASSET_REMOTE_END,\n            operationId,\n            blockchain,\n        );\n\n        return {\n            messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n            messageData: { identityId, v, r, s, vs },\n        };\n    }\n\n    /**\n     * Builds default handleStoreRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0HandleStoreRequestCommand',\n            priority: COMMAND_PRIORITY.HIGHEST,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default HandleStoreRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/publish/sender/publish-replication-command.js",
    "content": "import { Semaphore } from 'async-mutex';\nimport {\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    OPERATION_REQUEST_STATUS,\n    NETWORK_MESSAGE_TYPES,\n    NETWORK_SIGNATURES_FOLDER,\n    PUBLISHER_NODE_SIGNATURES_FOLDER,\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    COMMAND_PRIORITY,\n} from '../../../../constants/constants.js';\nimport Command from '../../../command.js';\n\nconst replicationSemaphore = new Semaphore(3);\n\nclass PublishReplicationCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n        this.operationService = ctx.publishService;\n        this.shardingTableService = ctx.shardingTableService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.signatureService = ctx.signatureService;\n        this.cryptoService = ctx.cryptoService;\n        this.messagingService = ctx.messagingService;\n        this.pendingStorageService = ctx.pendingStorageService;\n\n        this.errorType = ERROR_TYPE.LOCAL_STORE.LOCAL_STORE_ERROR;\n    }\n\n    async execute(command) {\n        const { operationId, blockchain, datasetRoot, minimumNumberOfNodeReplications, batchSize } =\n            command.data;\n        this.logger.debug(\n            `Searching for shard for operationId: ${operationId}, dataset root: ${datasetRoot}`,\n        );\n        try {\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                blockchain,\n                OPERATION_ID_STATUS.FIND_NODES_START,\n            );\n\n            const minAckResponses = this.operationService.getMinAckResponses(\n                minimumNumberOfNodeReplications,\n            );\n\n            const networkProtocols = this.operationService.getNetworkProtocols();\n\n            const shardNodes = [];\n            let nodePartOfShard = false;\n            const currentPeerId = this.networkModuleManager.getPeerId().toB58String();\n\n            const foundNodes = await this.findShardNodes(blockchain);\n\n            for (const node of foundNodes) {\n                if (node.id === currentPeerId) {\n                    nodePartOfShard = true;\n                } else {\n                    shardNodes.push({ id: node.id, protocol: networkProtocols[0] });\n                }\n            }\n\n            this.logger.debug(\n                `Found ${\n                    shardNodes.length + (nodePartOfShard ? 1 : 0)\n                } node(s) for operationId: ${operationId}`,\n            );\n\n            this.logger.trace(\n                `Found shard: ${JSON.stringify(\n                    shardNodes.map((node) => node.id),\n                    null,\n                    2,\n                )}`,\n            );\n\n            if (shardNodes.length + (nodePartOfShard ? 1 : 0) < minAckResponses) {\n                await this.handleError(\n                    operationId,\n                    blockchain,\n                    `Unable to find enough nodes for operationId: ${operationId}. Minimum number of nodes required: ${minAckResponses}`,\n                    this.errorType,\n                    true,\n                );\n\n                this.operationIdService.emitChangeEvent(\n                    OPERATION_ID_STATUS.FAILED,\n                    operationId,\n                    blockchain,\n                );\n                return Command.empty();\n            }\n\n            try {\n                await this.operationIdService.updateOperationIdStatus(\n                    operationId,\n                    blockchain,\n                    OPERATION_ID_STATUS.PUBLISH.PUBLISH_REPLICATE_START,\n                );\n                const batchSizePar = this.operationService.getBatchSize(batchSize);\n\n                const { identityId, v, r, s, vs } = await this.createSignatures(\n                    blockchain,\n                    nodePartOfShard,\n                    datasetRoot,\n                    operationId,\n                );\n\n                const updatedData = {\n                    ...command.data,\n                    batchSize: batchSizePar,\n                    minAckResponses,\n                    numberOfFoundNodes: shardNodes.length + (nodePartOfShard ? 1 : 0),\n                };\n                // eslint-disable-next-line no-param-reassign\n                command.data = updatedData;\n                if (nodePartOfShard) {\n                    await this.operationService.processResponse(\n                        { ...command, data: updatedData },\n                        OPERATION_REQUEST_STATUS.COMPLETED,\n                        {\n                            messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n                            messageData: { identityId, v, r, s, vs },\n                        },\n                        null,\n                    );\n                }\n            } catch (e) {\n                await this.handleError(operationId, blockchain, e.message, this.errorType, true);\n                this.operationIdService.emitChangeEvent(\n                    OPERATION_ID_STATUS.FAILED,\n                    operationId,\n                    blockchain,\n                );\n                return Command.empty();\n            }\n            const { dataset } = await this.operationIdService.getCachedOperationIdData(operationId);\n\n            await this.pendingStorageService.cacheDataset(\n                operationId,\n                datasetRoot,\n                dataset,\n                currentPeerId,\n            );\n\n            const message = {\n                dataset: dataset.public,\n                datasetRoot,\n                blockchain,\n            };\n\n            const replicationBatchSize = minAckResponses + 2;\n\n            await replicationSemaphore.runExclusive(async () => {\n                this.logger.info(\n                    `[REPLICATION] Starting for operationId: ${operationId}, ` +\n                        `shard: ${shardNodes.length} nodes, batch: ${replicationBatchSize}, min ACKs: ${minAckResponses}`,\n                );\n\n                for (let i = 0; i < shardNodes.length; i += replicationBatchSize) {\n                    if (i > 0) {\n                        // eslint-disable-next-line no-await-in-loop\n                        const record = await this.operationIdService.getOperationIdRecord(\n                            operationId,\n                        );\n                        if (record?.minAcksReached) {\n                            this.logger.info(\n                                `[REPLICATION] Minimum replication reached after ${i} nodes, ` +\n                                    `skipping remaining ${\n                                        shardNodes.length - i\n                                    } for operationId: ${operationId}`,\n                            );\n                            break;\n                        }\n                    }\n\n                    const batch = shardNodes.slice(i, i + replicationBatchSize);\n                    this.logger.debug(\n                        `Sending replication batch ${Math.floor(i / replicationBatchSize) + 1} ` +\n                            `(${batch.length} nodes) for operationId: ${operationId}`,\n                    );\n\n                    // eslint-disable-next-line no-await-in-loop\n                    await Promise.all(\n                        batch.map((node) =>\n                            this.sendAndHandleMessage(node, operationId, message, command),\n                        ),\n                    );\n                }\n            });\n        } catch (e) {\n            await this.handleError(operationId, blockchain, e.message, this.errorType, true);\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.FAILED,\n                operationId,\n                blockchain,\n            );\n            return Command.empty();\n        }\n\n        return Command.empty();\n    }\n\n    async sendAndHandleMessage(node, operationId, message, command) {\n        try {\n            let response = await this.messagingService.sendProtocolMessage(\n                node,\n                operationId,\n                message,\n                NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST,\n                NETWORK_MESSAGE_TIMEOUT_MILLS.PUBLISH.REQUEST,\n            );\n\n            if (response.header.messageType !== NETWORK_MESSAGE_TYPES.RESPONSES.ACK) {\n                const preRetryRecord = await this.operationIdService.getOperationIdRecord(\n                    operationId,\n                );\n                if (preRetryRecord?.minAcksReached) return;\n\n                this.logger.info(\n                    `[REPLICATION] Peer ${node.id} NACK for operationId: ${operationId}: ` +\n                        `${response.data?.errorMessage || 'unknown reason'}, retrying...`,\n                );\n                response = await this.messagingService.sendProtocolMessage(\n                    node,\n                    operationId,\n                    message,\n                    NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST,\n                    NETWORK_MESSAGE_TIMEOUT_MILLS.PUBLISH.REQUEST,\n                );\n            }\n\n            const responseData = response.data;\n            if (response.header.messageType === NETWORK_MESSAGE_TYPES.RESPONSES.ACK) {\n                await this.signatureService.addSignatureToStorage(\n                    NETWORK_SIGNATURES_FOLDER,\n                    operationId,\n                    responseData.identityId,\n                    responseData.v,\n                    responseData.r,\n                    responseData.s,\n                    responseData.vs,\n                );\n                await this.operationService.processResponse(\n                    command,\n                    OPERATION_REQUEST_STATUS.COMPLETED,\n                    responseData,\n                );\n            } else {\n                this.logger.warn(\n                    `[REPLICATION] Peer ${node.id} failed after retry for operationId: ${operationId}: ` +\n                        `${responseData?.errorMessage || 'unknown reason'}`,\n                );\n                await this.operationService.processResponse(\n                    command,\n                    OPERATION_REQUEST_STATUS.FAILED,\n                    responseData,\n                );\n            }\n        } catch (error) {\n            this.logger.warn(\n                `[REPLICATION] Peer ${node.id} error for operationId: ${operationId}: ${error.message}`,\n            );\n            await this.operationService.processResponse(command, OPERATION_REQUEST_STATUS.FAILED, {\n                errorMessage: error.message,\n            });\n        }\n    }\n\n    async findShardNodes(blockchainId) {\n        const shardNodes = await this.shardingTableService.findShard(\n            blockchainId,\n            true, // filter inactive nodes\n        );\n\n        // TODO: Optimize this so it's returned by shardingTableService.findShard\n        const nodesFound = await Promise.all(\n            shardNodes.map(({ peerId }) =>\n                this.shardingTableService.findPeerAddressAndProtocols(peerId),\n            ),\n        );\n\n        return nodesFound;\n    }\n\n    async createSignatures(blockchain, nodePartOfShard, datasetRoot, operationId) {\n        let v;\n        let r;\n        let s;\n        let vs;\n        const identityId = await this.blockchainModuleManager.getIdentityId(blockchain);\n        if (nodePartOfShard) {\n            ({ v, r, s, vs } = await this.signatureService.signMessage(blockchain, datasetRoot));\n            await this.signatureService.addSignatureToStorage(\n                NETWORK_SIGNATURES_FOLDER,\n                operationId,\n                identityId,\n                v,\n                r,\n                s,\n                vs,\n            );\n        }\n\n        const {\n            v: publisherNodeV,\n            r: publisherNodeR,\n            s: publisherNodeS,\n            vs: publisherNodeVS,\n        } = await this.signatureService.signMessage(\n            blockchain,\n            this.cryptoService.keccak256EncodePacked(\n                ['uint72', 'bytes32'],\n                [identityId, datasetRoot],\n            ),\n        );\n        await this.signatureService.addSignatureToStorage(\n            PUBLISHER_NODE_SIGNATURES_FOLDER,\n            operationId,\n            identityId,\n            publisherNodeV,\n            publisherNodeR,\n            publisherNodeS,\n            publisherNodeVS,\n        );\n        return { identityId, v, r, s, vs };\n    }\n\n    /**\n     * Builds default localStoreCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'publishReplicationCommand',\n            transactional: false,\n            priority: COMMAND_PRIORITY.HIGHEST,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PublishReplicationCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/receiver/v1.0.0/v1-0-0-handle-update-request-command.js",
    "content": "import HandleProtocolMessageCommand from '../../../common/handle-protocol-message-command.js';\n\nimport {\n    NETWORK_MESSAGE_TYPES,\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n} from '../../../../../constants/constants.js';\n\nclass HandleUpdateRequestCommand extends HandleProtocolMessageCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.updateService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.operationIdService = ctx.operationIdService;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.signatureService = ctx.signatureService;\n\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_LOCAL_STORE_REMOTE_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.UPDATE.UPDATE_LOCAL_STORE_REMOTE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.UPDATE.UPDATE_LOCAL_STORE_REMOTE_END;\n    }\n\n    async prepareMessage(commandData) {\n        const { blockchain, operationId, datasetRoot } = commandData;\n\n        const { dataset } = await this.operationIdService.getCachedOperationIdData(operationId);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_VALIDATE_ASSET_REMOTE_START,\n        );\n\n        const validationResult = await this.validateReceivedData(\n            operationId,\n            datasetRoot,\n            dataset,\n            blockchain,\n        );\n\n        this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_VALIDATE_ASSET_REMOTE_END,\n        );\n\n        if (validationResult.messageType === NETWORK_MESSAGE_TYPES.RESPONSES.NACK) {\n            return validationResult;\n        }\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_LOCAL_STORE_REMOTE_CACHE_DATASET_START,\n        );\n        await this.pendingStorageService.cacheDataset(operationId, datasetRoot, dataset);\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_LOCAL_STORE_REMOTE_CACHE_DATASET_END,\n        );\n\n        const identityId = await this.blockchainModuleManager.getIdentityId(blockchain);\n        const { v, r, s, vs } = await this.signatureService.signMessage(blockchain, datasetRoot);\n\n        return {\n            messageType: NETWORK_MESSAGE_TYPES.RESPONSES.ACK,\n            messageData: { identityId, v, r, s, vs },\n        };\n    }\n\n    /**\n     * Builds default handleUpdateRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0HandleUpdateRequestCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default HandleUpdateRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/sender/network-update-command.js",
    "content": "import NetworkProtocolCommand from '../../common/network-protocol-command.js';\nimport { ERROR_TYPE } from '../../../../constants/constants.js';\n\nclass NetworkUpdateCommand extends NetworkProtocolCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.blockchainModuleManager = ctx.blockchainModuleManager; // can we remove this\n        this.ualService = ctx.ualService; // can we remove this\n\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_NETWORK_START_ERROR;\n    }\n\n    /**\n     * Builds default networkUpdateCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'networkUpdateCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default NetworkUpdateCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/sender/update-find-shard-command.js",
    "content": "import FindShardCommand from '../../common/find-shard-command.js';\nimport { OPERATION_ID_STATUS, ERROR_TYPE } from '../../../../constants/constants.js';\n\nclass UpdateFindShardCommand extends FindShardCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.upateService;\n        this.errorType = ERROR_TYPE.FIND_SHARD.UPDATE_FIND_SHARD_ERROR;\n        this.operationStartEvent = OPERATION_ID_STATUS.UPDATE.UPDATE_FIND_NODES_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.UPDATE.UPDATE_FIND_NODES_END;\n    }\n\n    getOperationCommandSequence(nodePartOfShard) {\n        const sequence = [];\n        sequence.push('updateValidateAssetCommand');\n        if (nodePartOfShard) {\n            sequence.push('localUpdateCommand');\n        }\n        sequence.push('networkUpdateCommand');\n\n        return sequence;\n    }\n\n    /**\n     * Builds default updateFindShardCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateFindShardCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateFindShardCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/sender/update-schedule-messages-command.js",
    "content": "import ProtocolScheduleMessagesCommand from '../../common/protocol-schedule-messages-command.js';\nimport { OPERATION_ID_STATUS, ERROR_TYPE } from '../../../../constants/constants.js';\n\nclass UpdateScheduleMessagesCommand extends ProtocolScheduleMessagesCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.blockchainModuleManager = ctx.blockchainModuleManager; // can this be removed\n        this.repositoryModuleManager = ctx.repositoryModuleManager; // can this be removed\n\n        this.operationStartEvent = OPERATION_ID_STATUS.UPDATE.UPDATE_REPLICATE_START;\n        this.operationEndEvent = OPERATION_ID_STATUS.UPDATE.UPDATE_REPLICATE_END;\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_START_ERROR;\n    }\n\n    /**\n     * Builds default updateScheduleMessagesCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateScheduleMessagesCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateScheduleMessagesCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/sender/update-validate-asset-command.js",
    "content": "import ValidateAssetCommand from '../../../common/validate-asset-command.js';\nimport { OPERATION_ID_STATUS, ERROR_TYPE } from '../../../../constants/constants.js';\n\nclass UpdateValidateAssetCommand extends ValidateAssetCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.updateService;\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_VALIDATE_ASSET_ERROR;\n    }\n\n    async handleError(operationId, blockchain, errorMessage, errorType) {\n        await this.operationService.markOperationAsFailed(\n            operationId,\n            blockchain,\n            errorMessage,\n            errorType,\n        );\n    }\n\n    /**\n     * Executes command and produces one or more events\n     * @param command\n     */\n    async execute(command) {\n        const { operationId, blockchain, datasetRoot } = command.data;\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_VALIDATE_ASSET_START,\n        );\n\n        const cachedData = await this.operationIdService.getCachedOperationIdData(operationId);\n\n        await this.validationService.validateDatasetRoot(cachedData.dataset, datasetRoot);\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_VALIDATE_ASSET_END,\n        );\n        return this.continueSequence(\n            { ...command.data, retry: undefined, period: undefined },\n            command.sequence,\n        );\n    }\n\n    /**\n     * Builds default updateValidateAssetCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateValidateAssetCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateValidateAssetCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/sender/v1.0.0/v1-0-0-update-request-command.js",
    "content": "import ProtocolRequestCommand from '../../../common/protocol-request-command.js';\nimport {\n    NETWORK_MESSAGE_TIMEOUT_MILLS,\n    ERROR_TYPE,\n    NETWORK_SIGNATURES_FOLDER,\n} from '../../../../../constants/constants.js';\n\nclass PublishRequestCommand extends ProtocolRequestCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.updateService;\n        this.signatureService = ctx.signatureService;\n        this.operationIdService = ctx.operationIdService;\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_STORE_REQUEST_ERROR;\n    }\n\n    async prepareMessage(command) {\n        const { datasetRoot, operationId } = command.data;\n\n        // TODO: Backwards compatibility, send blockchain without chainId\n        const { blockchain } = command.data;\n\n        const { dataset } = await this.operationIdService.getCachedOperationIdData(operationId);\n\n        return {\n            dataset,\n            datasetRoot,\n            blockchain,\n        };\n    }\n\n    messageTimeout() {\n        return NETWORK_MESSAGE_TIMEOUT_MILLS.UPDATE.REQUEST;\n    }\n\n    async handleAck(command, responseData) {\n        const { operationId } = command.data;\n\n        await this.signatureService.addSignatureToStorage(\n            NETWORK_SIGNATURES_FOLDER,\n            operationId,\n            responseData.identityId,\n            responseData.v,\n            responseData.r,\n            responseData.s,\n            responseData.vs,\n        );\n\n        return super.handleAck(command, responseData);\n    }\n\n    /**\n     * Builds default publishRequestCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'v1_0_0PublishRequestCommand',\n            delay: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default PublishRequestCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/update-assertion-command.js",
    "content": "import { kcTools } from 'assertion-tools';\nimport Command from '../../command.js';\nimport {\n    // OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    TRIPLE_STORE_REPOSITORY,\n    TRIPLES_VISIBILITY,\n} from '../../../constants/constants.js';\n\nclass UpdateAssertionCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n        this.ualService = ctx.ualService;\n        this.dataService = ctx.dataService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_ASSERTION_ERROR;\n    }\n\n    async execute(command) {\n        const { operationId, ual, blockchain, assertion, firstNewKAIndex, updateStateIndex } =\n            command.data;\n        const validateCurrentData = this.validateCurrentData(ual);\n        if (this.validateCurrentData(validateCurrentData)) {\n            const preUpdateUalNamedGraphs =\n                // Old subjects old ual from select returned here probably {s, g}\n                await this.tripleStoreService.moveToHistoricAndDeleteAssertion(\n                    ual,\n                    updateStateIndex - 1,\n                );\n\n            await this.tripleStoreService.insertUpdatedKnowledgeCollection(\n                preUpdateUalNamedGraphs,\n                ual,\n                assertion,\n                firstNewKAIndex,\n            );\n        } else {\n            await this.handleError(\n                operationId,\n                blockchain,\n                `Data in current DKG doesn't match pre update data for ${ual}.`,\n                ERROR_TYPE.UPDATE_FINALIZATION.UPDATE_FINALIZATION_NO_OLD_DATA,\n                true,\n            );\n        }\n\n        return Command.empty();\n    }\n\n    // TODO: Move maybe outside of the command into metadata validation command (but it's not metadata)\n    async validateCurrentData(ual) {\n        const { blockchain, contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n        const assertionIds = await this.blockchainModuleManager.getKnowledgeCollectionMerkleRoot(\n            blockchain,\n            contract,\n            knowledgeCollectionId,\n        );\n        const assertionIdOfCurrent = assertionIds[assertionIds.length() - 2];\n\n        const preUpdateAssertion = await this.tripleStoreService.getKnowledgeAssetNamedGraph(\n            TRIPLE_STORE_REPOSITORY.DKG,\n            ual,\n            TRIPLES_VISIBILITY.PUBLIC,\n        );\n\n        const preUpdateMerkleRoot = kcTools.calculateMerkleRoot(preUpdateAssertion);\n\n        return assertionIdOfCurrent === preUpdateMerkleRoot;\n    }\n\n    /**\n     * Builds default updateAssertionCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateAssertionCommand',\n            delay: 0,\n            retries: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateAssertionCommand;\n"
  },
  {
    "path": "src/commands/protocols/update/update-validate-assertion-metadata-command.js",
    "content": "import ValidateAssertionMetadataCommand from '../common/validate-assertion-metadata-command.js';\nimport { OPERATION_ID_STATUS, ERROR_TYPE } from '../../../constants/constants.js';\n\nclass UpdateValidateAssertionMetadataCommand extends ValidateAssertionMetadataCommand {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_VALIDATE_ASSERTION_METADATA_ERROR;\n        this.operationStartEvent =\n            OPERATION_ID_STATUS.UPDATE_FINALIZATION.UPDATE_FINALIZATION_METADATA_VALIDATION_START;\n        this.operationEndEvent =\n            OPERATION_ID_STATUS.UPDATE_FINALIZATION.UPDATE_FINALIZATION_METADATA_VALIDATION_END;\n    }\n\n    /**\n     * Builds default updateValidateAssertionMetadataCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'updateValidateAssertionMetadataCommand',\n            delay: 0,\n            retries: 0,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default UpdateValidateAssertionMetadataCommand;\n"
  },
  {
    "path": "src/commands/query/query-command.js",
    "content": "import Command from '../command.js';\nimport {\n    TRIPLE_STORE_REPOSITORIES,\n    QUERY_TYPES,\n    OPERATION_ID_STATUS,\n    ERROR_TYPE,\n    COMMAND_PRIORITY,\n} from '../../constants/constants.js';\n\nclass QueryCommand extends Command {\n    constructor(ctx) {\n        super(ctx);\n        this.dataService = ctx.dataService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.paranetService = ctx.paranetService;\n        this.ualService = ctx.ualService;\n\n        this.errorType = ERROR_TYPE.QUERY.LOCAL_QUERY_ERROR;\n    }\n\n    async execute(command) {\n        const { operationId, queryType, paranetUAL } = command.data;\n        let { query, repository } = command.data;\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            null,\n            OPERATION_ID_STATUS.QUERY.QUERY_START,\n        );\n\n        let data;\n\n        if (paranetUAL) {\n            repository = this.paranetService.getParanetRepositoryName(paranetUAL);\n        }\n\n        // TODO: Review federated query logic for V8\n\n        // check if it's federated query\n        const pattern = /SERVICE\\s+<([^>]+)>/g;\n        const matches = query.match(pattern);\n        if (matches?.length > 0) {\n            for (const match of matches) {\n                const repositoryInOriginalQuery = match.split('<')[1].split('>')[0];\n                const repositoryName = this.validateRepositoryName(repositoryInOriginalQuery);\n                const federatedQueryRepositoryEndpoint =\n                    this.tripleStoreService.getRepositorySparqlEndpoint(repositoryName);\n                query = query.replace(repositoryInOriginalQuery, federatedQueryRepositoryEndpoint);\n            }\n        }\n\n        try {\n            switch (queryType) {\n                case QUERY_TYPES.CONSTRUCT: {\n                    if (Array.isArray(repository)) {\n                        const dataV6 = await this.tripleStoreService.construct(\n                            query,\n                            repository[0],\n                        );\n                        const dataV8 = await this.tripleStoreService.construct(\n                            query,\n                            repository[1],\n                        );\n\n                        data = this.dataService.removeDuplicateObjectsFromArray([\n                            ...dataV6,\n                            ...dataV8,\n                        ]);\n                    } else {\n                        data = await this.tripleStoreService.construct(query, repository);\n                    }\n                    break;\n                }\n                case QUERY_TYPES.SELECT: {\n                    if (Array.isArray(repository)) {\n                        const dataV6 = await this.tripleStoreService.select(query, repository[0]);\n                        const dataV8 = await this.tripleStoreService.select(query, repository[1]);\n\n                        data = this.dataService.removeDuplicateObjectsFromArray([\n                            ...dataV6,\n                            ...dataV8,\n                        ]);\n                    } else {\n                        data = await this.tripleStoreService.select(query, repository);\n                    }\n                    break;\n                }\n                default:\n                    throw new Error(`Unknown query type ${queryType}`);\n            }\n\n            await this.operationIdService.cacheOperationIdDataToMemory(operationId, data);\n\n            await this.operationIdService.cacheOperationIdDataToFile(operationId, data);\n\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                null,\n                OPERATION_ID_STATUS.QUERY.QUERY_END,\n            );\n\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                null,\n                OPERATION_ID_STATUS.COMPLETED,\n            );\n        } catch (e) {\n            await this.handleError(operationId, null, e.message, this.errorType, true);\n        }\n\n        return Command.empty();\n    }\n\n    validateRepositoryName(repository) {\n        let isParanetRepoValid = false;\n        if (this.ualService.isUAL(repository)) {\n            const paranetRepoName = this.paranetService.getParanetRepositoryName(repository);\n            isParanetRepoValid = this.config.assetSync?.syncParanets.includes(repository);\n            if (isParanetRepoValid) {\n                return paranetRepoName;\n            }\n        }\n        const isTripleStoreRepoValid =\n            Object.values(TRIPLE_STORE_REPOSITORIES).includes(repository);\n        if (isTripleStoreRepoValid) {\n            return repository;\n        }\n\n        if (!isParanetRepoValid && !isTripleStoreRepoValid) {\n            throw new Error(`Query failed! Repository with name: ${repository} doesn't exist`);\n        }\n    }\n\n    /**\n     * Builds default queryCommand\n     * @param map\n     * @returns {{add, data: *, delay: *, deadline: *}}\n     */\n    default(map) {\n        const command = {\n            name: 'queryCommand',\n            priority: COMMAND_PRIORITY.HIGHEST,\n            transactional: false,\n        };\n        Object.assign(command, map);\n        return command;\n    }\n}\n\nexport default QueryCommand;\n"
  },
  {
    "path": "src/constants/constants.js",
    "content": "import { BigNumber, ethers } from 'ethers';\nimport { createRequire } from 'module';\n\nexport const WS_RPC_PROVIDER_PRIORITY = 2;\n\nexport const HTTP_RPC_PROVIDER_PRIORITY = 1;\n\nexport const FALLBACK_PROVIDER_QUORUM = 1;\n\nexport const PUBLISH_BATCH_SIZE = 20;\n\nexport const PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS = 3;\n\nexport const GET_BATCH_SIZE = 2;\n\nexport const GET_MIN_NUM_OF_NODE_REPLICATIONS = 1;\n\nexport const FINALITY_BATCH_SIZE = 1;\n\nexport const FINALITY_MIN_NUM_OF_NODE_REPLICATIONS = 1;\n\nexport const ASK_BATCH_SIZE = 20;\n\nexport const RPC_PROVIDER_STALL_TIMEOUT = 60 * 1000;\n\nexport const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000;\n\nexport const UINT256_MAX_BN = ethers.constants.MaxUint256;\n\nexport const UINT128_MAX_BN = BigNumber.from(2).pow(128).sub(1);\n\nexport const UINT64_MAX_BN = BigNumber.from(2).pow(64).sub(1);\n\nexport const UINT40_MAX_BN = BigNumber.from(2).pow(40).sub(1);\n\nexport const UINT32_MAX_BN = BigNumber.from(2).pow(32).sub(1);\n\nexport const ONE_ETHER = BigNumber.from('1000000000000000000');\n\nexport const HASH_RING_SIZE = ethers.constants.MaxUint256;\n\nexport const STAKE_UINT256_MULTIPLIER_BN = UINT256_MAX_BN.div(500000000);\n\nexport const UINT256_UINT32_DIVISOR_BN = UINT256_MAX_BN.div(UINT32_MAX_BN);\n\nexport const ZERO_PREFIX = '0x';\n\nexport const ZERO_BYTES32 = ethers.constants.HashZero;\n\nexport const ZERO_ADDRESS = ethers.constants.AddressZero;\n\nexport const SCHEMA_CONTEXT = 'http://schema.org/';\n\nexport const PRIVATE_ASSERTION_PREDICATE =\n    'https://ontology.origintrail.io/dkg/1.0#privateMerkleRoot';\n\nexport const TRIPLE_ANNOTATION_LABEL_PREDICATE = 'https://ontology.origintrail.io/dkg/1.0#label';\n\nexport const PRIVATE_RESOURCE_PREDICATE =\n    'https://ontology.origintrail.io/dkg/1.0#representsPrivateResource';\n\nexport const DKG_METADATA_PREDICATES = {\n    PUBLISHED_BY: 'https://ontology.origintrail.io/dkg/1.0#publishedBy',\n    PUBLISHED_AT_BLOCK: 'https://ontology.origintrail.io/dkg/1.0#publishedAtBlock',\n    PUBLISH_TX: 'https://ontology.origintrail.io/dkg/1.0#publishTx',\n    PUBLISH_TIME: 'https://ontology.origintrail.io/dkg/1.0#publishTime',\n    BLOCK_TIME: 'https://ontology.origintrail.io/dkg/1.0#blockTime',\n};\n\nexport const PRIVATE_HASH_SUBJECT_PREFIX = 'https://ontology.origintrail.io/dkg/1.0#metadata-hash:';\n\nexport const UAL_PREDICATE = '<https://ontology.origintrail.io/dkg/1.0#UAL>';\n\nexport const COMMIT_BLOCK_DURATION_IN_BLOCKS = 5;\n\nexport const COMMITS_DELAY_BETWEEN_NODES_IN_BLOCKS = 5;\n\nexport const TRANSACTION_POLLING_TIMEOUT_MILLIS = 300 * 1000;\n\nexport const SOLIDITY_ERROR_STRING_PREFIX = '0x08c379a0';\n\nexport const SOLIDITY_PANIC_CODE_PREFIX = '0x4e487b71';\n\nexport const SOLIDITY_PANIC_REASONS = {\n    0x1: 'Assertion error',\n    0x11: 'Arithmetic operation underflowed or overflowed outside of an unchecked block',\n    0x12: 'Division or modulo division by zero',\n    0x21: 'Tried to convert a value into an enum, but the value was too big or negative',\n    0x22: 'Incorrectly encoded storage byte array',\n    0x31: '.pop() was called on an empty array',\n    0x32: 'Array accessed at an out-of-bounds or negative index',\n    0x41: 'Too much memory was allocated, or an array was created that is too large',\n    0x51: 'Called a zero-initialized variable of internal function type',\n};\n\nexport const LIBP2P_KEY_DIRECTORY = 'libp2p';\n\nexport const LIBP2P_KEY_FILENAME = 'privateKey';\n\nexport const BLS_KEY_DIRECTORY = 'bls';\n\nexport const BLS_KEY_FILENAME = 'secretKey';\n\nexport const TRIPLE_STORE_CONNECT_MAX_RETRIES = 10;\n\nexport const COMMAND_PRIORITY = {\n    HIGHEST: 0,\n    HIGH: 1,\n    MEDIUM: 5,\n    LOW: 10,\n    LOWEST: 20,\n};\n\nexport const DEFAULT_COMMAND_PRIORITY = COMMAND_PRIORITY.MEDIUM;\n\nexport const DEFAULT_BLOCKCHAIN_EVENT_SYNC_PERIOD_IN_MILLS = 15 * 24 * 60 * 60 * 1000; // 15 days\n\nexport const MAX_BLOCKCHAIN_EVENT_SYNC_OF_HISTORICAL_BLOCKS_IN_MILLS = 60 * 60 * 1000; // 1 hour\n\nexport const MAXIMUM_NUMBERS_OF_BLOCKS_TO_FETCH = 50;\n\nexport const TRANSACTION_QUEUE_CONCURRENCY = 1;\n\nexport const TRIPLE_STORE_CONNECT_RETRY_FREQUENCY = 10;\n\nexport const MAX_FILE_SIZE = 10000000;\n\nexport const GET_STATES = { LATEST: 'LATEST', FINALIZED: 'LATEST_FINALIZED' };\n\nexport const BYTES_IN_KILOBYTE = 1024;\n\nexport const BYTES_IN_MEGABYTE = BYTES_IN_KILOBYTE * BYTES_IN_KILOBYTE;\n\nexport const PUBLISH_TYPES = { ASSERTION: 'assertion', ASSET: 'asset', INDEX: 'index' };\n\nexport const DEFAULT_GET_STATE = GET_STATES.LATEST;\n\nexport const PEER_OFFLINE_LIMIT = 24 * 60 * 60 * 1000;\n\nexport const CONTENT_ASSET_HASH_FUNCTION_ID = 1;\n\nexport const CHUNK_BYTE_SIZE = 32;\n\nexport const PARANET_SYNC_KA_COUNT = 50;\nexport const PARANET_SYNC_RETRIES_LIMIT = 3;\nexport const PARANET_SYNC_RETRY_DELAY_MS = 60 * 1000;\n\nexport const PARANET_ACCESS_POLICY = {\n    OPEN: 0,\n    PERMISSIONED: 1,\n};\n\nexport const TRIPLE_STORE_REPOSITORIES = {\n    DKG: 'dkg',\n    PUBLIC_CURRENT: 'publicCurrent',\n    PRIVATE_CURRENT: 'privateCurrent',\n};\n\nexport const BASE_NAMED_GRAPHS = {\n    UNIFIED: 'unified:graph',\n    HISTORICAL_UNIFIED: 'historical-unified:graph',\n    METADATA: 'metadata:graph',\n    CURRENT: 'current:graph',\n};\n\nexport const REQUIRED_MODULES = [\n    'repository',\n    'httpClient',\n    'network',\n    'validation',\n    'blockchain',\n    'tripleStore',\n    'blockchainEventsService',\n];\n\n/**\n * Triple store media types\n * @type {{APPLICATION_JSON: string, N_QUADS: string, SPARQL_RESULTS_JSON: string, LD_JSON: string}}\n */\nexport const MEDIA_TYPES = {\n    LD_JSON: 'application/ld+json',\n    N_QUADS: 'application/n-quads',\n    SPARQL_RESULTS_JSON: 'application/sparql-results+json',\n    JSON: 'application/json',\n};\n\n/**\n * XML data types\n * @type {{FLOAT: string, DECIMAL: string, DOUBLE: string, BOOLEAN: string, INTEGER: string}}\n */\nexport const XML_DATA_TYPES = {\n    DECIMAL: 'http://www.w3.org/2001/XMLSchema#decimal',\n    FLOAT: 'http://www.w3.org/2001/XMLSchema#float',\n    DOUBLE: 'http://www.w3.org/2001/XMLSchema#double',\n    INTEGER: 'http://www.w3.org/2001/XMLSchema#integer',\n    BOOLEAN: 'http://www.w3.org/2001/XMLSchema#boolean',\n};\n\nexport const MIN_NODE_VERSION = 16;\n\nexport const NETWORK_API_RATE_LIMIT = {\n    TIME_WINDOW_MILLS: 1 * 60 * 1000,\n    MAX_NUMBER: 100,\n};\n\nexport const NETWORK_API_SPAM_DETECTION = {\n    TIME_WINDOW_MILLS: 1 * 60 * 1000,\n    MAX_NUMBER: 150,\n};\n\nexport const NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES = 60;\n\nexport const HIGH_TRAFFIC_OPERATIONS_NUMBER_PER_HOUR = 16000;\n\nexport const SHARDING_TABLE_CHECK_COMMAND_FREQUENCY_MILLS = 10 * 1000; // 10 seconds\n\nexport const PARANET_SYNC_FREQUENCY_MILLS = 1 * 60 * 1000;\n\nexport const SEND_TELEMETRY_COMMAND_FREQUENCY_MINUTES = 15;\n\nexport const PEER_RECORD_UPDATE_DELAY = 30 * 60 * 1000; // 30 minutes\n\nexport const DEFAULT_COMMAND_CLEANUP_TIME_MILLS = 4 * 24 * 60 * 60 * 1000;\n\nexport const REMOVE_SESSION_COMMAND_DELAY = 2 * 60 * 1000;\n\nexport const OPERATION_IDS_COMMAND_CLEANUP_TIME_MILLS = 24 * 60 * 60 * 1000;\n\nexport const GET_LATEST_SERVICE_AGREEMENT_FREQUENCY_MILLS = 30 * 1000;\n\nexport const DIAL_PEERS_COMMAND_FREQUENCY_MILLS = 30 * 1000;\n\nexport const DIAL_PEERS_CONCURRENCY = 10;\n\nexport const MIN_DIAL_FREQUENCY_MILLIS = 60 * 60 * 1000;\n\nexport const PERMANENT_COMMANDS = [\n    'eventListenerCommand',\n    'otnodeUpdateCommand',\n    'sendTelemetryCommand',\n    'startParanetSyncCommands',\n    'dialPeersCommand',\n    'shardingTableCheckCommand',\n    'commandsCleanerCommand',\n    'operationIdCleanerCommand',\n    'blockchainEventCleanerCommand',\n    'getCleanerCommand',\n    'getResponseCleanerCommand',\n    'publishCleanerCommand',\n    'publishResponseCleanerCommand',\n    'pendingStorageCleanerCommand',\n    'finalityCleanerCommand',\n    'finalityResponseCleanerCommand',\n    'askCleanerCommand',\n    'askResponseCleanerCommand',\n    'batchGetCleanerCommand',\n];\n\nexport const MAX_COMMAND_DELAY_IN_MILLS = 14400 * 60 * 1000; // 10 days\n\nexport const DEFAULT_COMMAND_REPEAT_INTERVAL_IN_MILLS = 5000; // 5 seconds\n\nexport const DEFAULT_COMMAND_DELAY_IN_MILLS = 60 * 1000; // 60 seconds\n\nexport const TRANSACTION_PRIORITY = {\n    HIGHEST: 0,\n    HIGH: 1,\n    MEDIUM: 5,\n    LOW: 10,\n    LOWEST: 20,\n};\n\nexport const V0_PRIVATE_ASSERTION_PREDICATE =\n    'https://ontology.origintrail.io/dkg/1.0#privateAssertionID';\n\nexport const DKG_PREDICATE = 'https://ontology.origintrail.io/dkg/1.0#';\nexport const HAS_NAMED_GRAPH_SUFFIX = 'hasNamedGraph';\nexport const HAS_KNOWLEDGE_ASSET_SUFFIX = 'hasKnowledgeAsset';\n\nconst require = createRequire(import.meta.url);\n\nexport const ABIs = {\n    KnowledgeCollection: require('dkg-evm-module/abi/KnowledgeCollection.json'),\n    KnowledgeCollectionStorage: require('dkg-evm-module/abi/KnowledgeCollectionStorage.json'),\n    Staking: require('dkg-evm-module/abi/Staking.json'),\n    Token: require('dkg-evm-module/abi/Token.json'),\n    Hub: require('dkg-evm-module/abi/Hub.json'),\n    IdentityStorage: require('dkg-evm-module/abi/IdentityStorage.json'),\n    ParametersStorage: require('dkg-evm-module/abi/ParametersStorage.json'),\n    Profile: require('dkg-evm-module/abi/Profile.json'),\n    ProfileStorage: require('dkg-evm-module/abi/ProfileStorage.json'),\n    ShardingTable: require('dkg-evm-module/abi/ShardingTable.json'),\n    ShardingTableStorage: require('dkg-evm-module/abi/ShardingTableStorage.json'),\n    ParanetsRegistry: require('dkg-evm-module/abi/ParanetsRegistry.json'),\n    ParanetKnowledgeCollectionsRegistry: require('dkg-evm-module/abi/ParanetKnowledgeCollectionsRegistry.json'),\n    AskStorage: require('dkg-evm-module/abi/AskStorage.json'),\n    Chronos: require('dkg-evm-module/abi/Chronos.json'),\n    Paranet: require('dkg-evm-module/abi/Paranet.json'),\n    RandomSampling: require('dkg-evm-module/abi/RandomSampling.json'),\n    RandomSamplingStorage: require('dkg-evm-module/abi/RandomSamplingStorage.json'),\n    DelegatorsInfo: require('dkg-evm-module/abi/DelegatorsInfo.json'),\n};\n\nexport const CONTRACT_FUNCTION_PRIORITY = {};\n\nexport const COMMAND_RETRIES = {};\n\nexport const SIMPLE_ASSET_SYNC_PARAMETERS = {\n    GET_RESULT_POLLING_INTERVAL_MILLIS: 1 * 1000,\n    GET_RESULT_POLLING_MAX_ATTEMPTS: 30,\n};\n\nexport const PARANET_SYNC_PARAMETERS = {\n    GET_RESULT_POLLING_INTERVAL_MILLIS: 1 * 1000,\n    GET_RESULT_POLLING_MAX_ATTEMPTS: 300,\n};\n\nexport const COMMAND_TX_GAS_INCREASE_FACTORS = {\n    SUBMIT_COMMIT: 1.2,\n    SUBMIT_UPDATE_COMMIT: 1.2,\n    SUBMIT_PROOFS: 1.2,\n};\n\nexport const MIGRATION_FLAG_PATH = '.enrichment_migration_done_dkg';\n\nexport const CONTRACT_FUNCTION_GAS_LIMIT_INCREASE_FACTORS = {};\n\nexport const GNOSIS_DEFAULT_GAS_PRICE = {\n    TESTNET: 1,\n    MAINNET: 1,\n};\n\nexport const NEURO_DEFAULT_GAS_PRICE = {\n    TESTNET: 8,\n    MAINNET: 8,\n};\n\nexport const CONTRACT_FUNCTION_FIXED_GAS_PRICE = {};\n\nexport const WEBSOCKET_PROVIDER_OPTIONS = {\n    reconnect: {\n        auto: true,\n        delay: 1000, // ms\n        maxAttempts: 3,\n    },\n    clientConfig: {\n        keepalive: true,\n        keepaliveInterval: 30 * 1000, // ms\n    },\n};\n\nexport const TRIPLE_STORE_IMPLEMENTATION = {\n    BLAZEGRAPH: 'Blazegraph',\n    GRAPHDB: 'GraphDB',\n    FUSEKI: 'Fuseki',\n    NEPTUNE: 'Neptune',\n};\n\nexport const NETWORK_MESSAGE_TYPES = {\n    REQUESTS: {\n        PROTOCOL_REQUEST: 'PROTOCOL_REQUEST',\n    },\n    RESPONSES: {\n        ACK: 'ACK',\n        NACK: 'NACK',\n        BUSY: 'BUSY',\n    },\n};\n\nexport const PARANET_NODES_ACCESS_POLICIES = ['OPEN', 'PERMISSIONED'];\n\nexport const NETWORK_MESSAGE_TIMEOUT_MILLS = {\n    PUBLISH: {\n        REQUEST: 60 * 1000,\n    },\n    UPDATE: {\n        REQUEST: 60 * 1000,\n    },\n    GET: {\n        REQUEST: 15 * 1000,\n    },\n    ASK: {\n        REQUEST: 60 * 1000,\n    },\n    FINALITY: {\n        REQUEST: 60 * 1000,\n    },\n    BATCH_GET: {\n        REQUEST: 30 * 1000,\n    },\n};\n\nexport const MAX_OPEN_SESSIONS = 10;\n\nexport const ERROR_TYPE = {\n    EVENT_LISTENER_ERROR: 'EventListenerError',\n    BLOCKCHAIN_EVENT_LISTENER_ERROR: 'BlockchainEventListenerError',\n    DIAL_PROTOCOL_ERROR: 'DialProtocolError',\n    VALIDATE_ASSET_ERROR: 'ValidateAssetError',\n    NETWORK_PROTOCOL_ERROR: 'NetworkProtocolError',\n    PUBLISH: {\n        PUBLISH_START_ERROR: 'PublishStartError',\n        PUBLISH_ROUTE_ERROR: 'PublishRouteError',\n        PUBLISH_NETWORK_START_ERROR: 'PublishNetworkStartError',\n        PUBLISH_VALIDATE_ASSET_ERROR: 'PublishValidateAssetError',\n        PUBLISH_LOCAL_STORE_ERROR: 'PublishLocalStoreError',\n        PUBLISH_LOCAL_STORE_REMOTE_ERROR: 'PublishLocalStoreRemoteError',\n        PUBLISH_FIND_NODES_ERROR: 'PublishFindNodesError',\n        PUBLISH_STORE_REQUEST_ERROR: 'PublishStoreRequestError',\n        PUBLISH_VALIDATE_ASSERTION_METADATA_ERROR: 'PublishValidateAssertionMetadataError',\n        PUBLISH_ERROR: 'PublishError',\n    },\n    STORE_ASSERTION_ERROR: 'StoreAssertionError',\n    UPDATE: {\n        UPDATE_INIT_ERROR: 'UpdateInitError',\n        UPDATE_REQUEST_ERROR: 'UpdateRequestError',\n        UPDATE_START_ERROR: 'UpdateStartError',\n        UPDATE_ROUTE_ERROR: 'UpdateRouteError',\n        UPDATE_LOCAL_STORE_ERROR: 'UpdateLocalStoreError',\n        UPDATE_LOCAL_STORE_REMOTE_ERROR: 'UpdateLocalStoreRemoteError',\n        UPDATE_ERROR: 'UpdateError',\n        UPDATE_STORE_INIT_ERROR: 'UpdateStoreInitError',\n        UPDATE_REMOTE_ERROR: 'UpdateRemoteError',\n        UPDATE_DELETE_PENDING_STATE_ERROR: 'UpdateDeletePendingStateError',\n        UPDATE_VALIDATE_ASSET_ERROR: 'UpdateValidateAssetError',\n        UPDATE_STORE_REQUEST_ERROR: 'UpdateStoreRequestError',\n        UPDATE_VALIDATE_ASSERTION_METADATA_ERROR: 'UpadateValidateAssertionMetadataError',\n        UPDATE_ASSERTION_ERROR: 'UpdateAssertionError',\n        UPDATE_NETWORK_START_ERROR: 'UpdateNetworkStartError',\n    },\n    GET: {\n        GET_ROUTE_ERROR: 'GetRouteError',\n        GET_ASSERTION_ID_ERROR: 'GetAssertionIdError',\n        GET_PRIVATE_ASSERTION_ID_ERROR: 'GetPrivateAssertionIdError',\n        GET_VALIDATE_ASSET_ERROR: 'GetValidateAssetError',\n        GET_LOCAL_ERROR: 'GetLocalError',\n        GET_NETWORK_ERROR: 'GetNetworkError',\n        GET_CURATED_PARANET_NETWORK_ERROR: 'GetCuratedParanetNetworkError',\n        GET_START_ERROR: 'GetStartError',\n        GET_INIT_ERROR: 'GetInitError',\n        GET_REQUEST_ERROR: 'GetRequestError',\n        GET_INIT_REMOTE_ERROR: 'GetInitRemoteError',\n        GET_REQUEST_REMOTE_ERROR: 'GetRequestRemoteError',\n        GET_ERROR: 'GetError',\n    },\n    BATCH_GET: {\n        BATCH_GET_ERROR: 'BatchGetError',\n    },\n    LOCAL_STORE: {\n        LOCAL_STORE_ERROR: 'LocalStoreError',\n    },\n    QUERY: {\n        LOCAL_QUERY_ERROR: 'LocalQueryError',\n    },\n    GET_BID_SUGGESTION: {\n        UNSUPPORTED_BID_SUGGESTION_RANGE_ERROR: 'UnsupportedBidSuggestionRangeError',\n    },\n    PARANET: {\n        START_PARANET_SYNC_ERROR: 'StartParanetSyncError',\n        PARANET_SYNC_ERROR: 'ParanetSyncError',\n    },\n    FIND_SHARD: {\n        FIND_SHARD_ERROR: 'FindShardError',\n        PUBLISH_FIND_SHARD_ERROR: 'PublishFindShardError',\n        UPDATE_FIND_SHARD_ERROR: 'UpdateFindShardError',\n        GET_FIND_SHARD_ERROR: 'GetFindShardError',\n        BATCH_GET_FIND_SHARD_ERROR: 'BatchGetFindShardError',\n    },\n    ASK: {\n        ASK_ERROR: 'AskError',\n        ASK_NETWORK_ERROR: 'AskNetworkError',\n        ASK_REQUEST_ERROR: 'AskRequestError',\n        ASK_REQUEST_REMOTE_ERROR: 'AskRequestRemoteError',\n        ASK_FIND_SHARD_ERROR: 'AskFindShardError',\n    },\n    PUBLISH_FINALIZATION: {\n        PUBLISH_FINALIZATION_NO_CACHED_DATA: 'PublishFinalizationNoCachedData',\n    },\n    UPDATE_FINALIZATION: {\n        UPDATE_FINALIZATION_NO_CACHED_DATA: 'UpdateFinalizationNoCachedData',\n        UPDATE_FINALIZATION_NO_OLD_DATA: 'UpdateFinalizationNoOldData',\n    },\n    FINALITY: {\n        FINALITY_ERROR: 'FinalityError',\n        FINALITY_NETWORK_ERROR: 'FinalityNetworkError',\n        FINALITY_REQUEST_ERROR: 'FinalityRequestError',\n        FINALITY_REQUEST_REMOTE_ERROR: 'FinalityRequestRemoteError',\n        FINALITY_START_ERROR: 'FinalityStartError',\n    },\n};\nexport const OPERATION_ID_STATUS = {\n    PENDING: 'PENDING',\n    FAILED: 'FAILED',\n    COMPLETED: 'COMPLETED',\n    FIND_NODES_START: 'FIND_NODES_START',\n    FIND_NODES_END: 'FIND_NODES_END',\n    FIND_CURATED_PARANET_NODES_START: 'FIND_CURATED_PARANET_NODES_START',\n    FIND_CURATED_PARANET_NODES_END: 'FIND_CURATED_PARANET_NODES_END',\n    DIAL_PROTOCOL_START: 'DIAL_PROTOCOL_START',\n    DIAL_PROTOCOL_END: 'DIAL_PROTOCOL_END',\n    VALIDATE_ASSET_START: 'VALIDATE_ASSET_START',\n    VALIDATE_ASSET_END: 'VALIDATE_ASSET_END',\n    VALIDATE_ASSET_BLOCKCHAIN_START: 'VALIDATE_ASSET_BLOCKCHAIN_START',\n    VALIDATE_ASSET_BLOCKCHAIN_END: 'VALIDATE_ASSET_BLOCKCHAIN_END',\n    VALIDATE_ASSERTION_METADATA_START: 'VALIDATE_ASSERTION_METADATA_START',\n    VALIDATE_ASSERTION_METADATA_END: 'VALIDATE_ASSERTION_METADATA_END',\n    PROTOCOL_SCHEDULE_MESSAGE_START: 'PROTOCOL_SCHEDULE_MESSAGE_START',\n    PROTOCOL_SCHEDULE_MESSAGE_END: 'PROTOCOL_SCHEDULE_MESSAGE_END',\n    HANDLE_PROTOCOL_MESSAGE_START: 'HANDLE_PROTOCOL_MESSAGE_START',\n    HANDLE_PROTOCOL_MESSAGE_END: 'HANDLE_PROTOCOL_MESSAGE_END',\n    PUBLISH_LOCAL_STORE_REMOTE_SEND_MESSAGE_RESPONSE_START:\n        'PUBLISH_LOCAL_STORE_REMOTE_SEND_MESSAGE_RESPONSE_START',\n    PUBLISH_LOCAL_STORE_REMOTE_SEND_MESSAGE_RESPONSE_END:\n        'PUBLISH_LOCAL_STORE_REMOTE_SEND_MESSAGE_RESPONSE_END',\n    PUBLISH: {\n        VALIDATING_PUBLISH_ASSERTION_REMOTE_START: 'VALIDATING_PUBLISH_ASSERTION_REMOTE_START',\n        VALIDATING_PUBLISH_ASSERTION_REMOTE_END: 'VALIDATING_PUBLISH_ASSERTION_REMOTE_END',\n        PUBLISH_VALIDATE_ASSET_START: 'PUBLISH_VALIDATE_ASSET_START',\n        PUBLISH_VALIDATE_ASSET_END: 'PUBLISH_VALIDATE_ASSET_END',\n        PUBLISH_VALIDATE_ASSET_PARANET_EXISTS_START: 'PUBLISH_VALIDATE_ASSET_PARANET_EXISTS_START',\n        PUBLISH_VALIDATE_ASSET_PARANET_EXISTS_END: 'PUBLISH_VALIDATE_ASSET_PARANET_EXISTS_END',\n        PUBLISH_VALIDATE_ASSET_NODES_ACCESS_POLICY_CHECK_START:\n            'PUBLISH_VALIDATE_ASSET_NODES_ACCESS_POLICY_CHECK_START',\n        PUBLISH_VALIDATE_ASSET_NODES_ACCESS_POLICY_CHECK_END:\n            'PUBLISH_VALIDATE_ASSET_NODES_ACCESS_POLICY_CHECK_END',\n        INSERTING_ASSERTION: 'INSERTING_ASSERTION',\n        PUBLISHING_ASSERTION: 'PUBLISHING_ASSERTION',\n        PUBLISH_START: 'PUBLISH_START',\n        PUBLISH_INIT_START: 'PUBLISH_INIT_START',\n        PUBLISH_INIT_END: 'PUBLISH_INIT_END',\n        PUBLISH_LOCAL_STORE_REMOTE_CACHE_DATASET_START:\n            'PUBLISH_LOCAL_STORE_REMOTE_CACHE_DATASET_START',\n        PUBLISH_LOCAL_STORE_REMOTE_CACHE_DATASET_END:\n            'PUBLISH_LOCAL_STORE_REMOTE_CACHE_DATASET_END',\n        PUBLISH_REPLICATE_START: 'PUBLISH_REPLICATE_START',\n        PUBLISH_REPLICATE_END: 'PUBLISH_REPLICATE_END',\n        PUBLISH_FIND_NODES_START: 'PUBLISH_FIND_NODES_START',\n        PUBLISH_FIND_NODES_END: 'PUBLISH_FIND_NODES_END',\n        PUBLISH_END: 'PUBLISH_END',\n        PUBLISH_LOCAL_STORE_REMOTE_START: 'PUBLISH_LOCAL_STORE_REMOTE_START',\n        PUBLISH_LOCAL_STORE_REMOTE_END: 'PUBLISH_LOCAL_STORE_REMOTE_END',\n        PUBLISH_VALIDATE_ASSET_REMOTE_START: 'VALIDATE_ASSET_REMOTE_START',\n        PUBLISH_VALIDATE_ASSET_REMOTE_END: 'VALIDATE_ASSET_REMOTE_END',\n        PUBLISH_FAILED: 'PUBLISH_FAILED',\n    },\n    PUBLISH_FINALIZATION: {\n        PUBLISH_FINALIZATION_START: 'PUBLISH_FINALIZATION_START',\n        PUBLISH_FINALIZATION_METADATA_VALIDATION_START:\n            'PUBLISH_FINALIZATION_METADATA_VALIDATION_START',\n        PUBLISH_FINALIZATION_METADATA_VALIDATION_END:\n            'PUBLISH_FINALIZATION_METADATA_VALIDATION_END',\n        PUBLISH_FINALIZATION_STORE_ASSERTION_START: 'PUBLISH_FINALIZATION_STORE_ASSERTION_START',\n        PUBLISH_FINALIZATION_STORE_ASSERTION_END: 'PUBLISH_FINALIZATION_STORE_ASSERTION_END',\n        PUBLISH_FINALIZATION_END: 'PUBLISH_FINALIZATION_END',\n        PUBLISH_FINALIZATION_FAILED: 'PUBLISH_FINALIZATION_FAILED',\n    },\n    UPDATE_FINALIZATION: {\n        UPDATE_FINALIZATION_START: 'UPDATE_FINALIZATION_START',\n        UPDATE_FINALIZATION_METADATA_VALIDATION_START:\n            'UPDATE_FINALIZATION_METADATA_VALIDATION_START',\n        UPDATE_FINALIZATION_METADATA_VALIDATION_END: 'UPDATE_FINALIZATION_METADATA_VALIDATION_END',\n        UPDATE_FINALIZATION_STORE_ASSERTION_START: 'UPDATE_FINALIZATION_STORE_ASSERTION_START',\n        UPDATE_FINALIZATION_STORE_ASSERTION_END: 'UPDATE_FINALIZATION_STORE_ASSERTION_END',\n        UPDATE_FINALIZATION_END: 'UPDATE_FINALIZATION_END',\n    },\n    UPDATE: {\n        UPDATE_START: 'UPDATE_START',\n        UPDATE_INIT_START: 'UPDATE_INIT_START',\n        UPDATE_INIT_END: 'UPDATE_INIT_END',\n        UPDATE_REPLICATE_START: 'UPDATE_REPLICATE_START',\n        UPDATE_REPLICATE_END: 'UPDATE_REPLICATE_END',\n        UPDATE_FIND_NODES_START: 'UPDATE_FIND_NODES_START',\n        UPDATE_FIND_NODES_END: 'UPDATE_FIND_NODES_END',\n        VALIDATING_UPDATE_ASSERTION_REMOTE_START: 'VALIDATING_UPDATE_ASSERTION_REMOTE_START',\n        VALIDATING_UPDATE_ASSERTION_REMOTE_END: 'VALIDATING_UPDATE_ASSERTION_REMOTE_END',\n        UPDATE_END: 'UPDATE_END',\n        UPDATE_VALIDATE_ASSET_START: 'UPDATE_VALIDATE_ASSET_START',\n        UPDATE_VALIDATE_ASSET_END: 'UPDATE_VALIDATE_ASSET_END',\n        UPDATE_NETWORK_START_ERROR: 'UPDATE_NETWORK_START_ERROR',\n        UPDATE_LOCAL_STORE_REMOTE_START: 'UPDATE_LOCAL_STORE_REMOTE_START',\n        UPDATE_LOCAL_STORE_REMOTE_END: 'UPDATE_LOCAL_STORE_REMOTE_END',\n        UPDATE_VALIDATE_ASSET_REMOTE_START: 'UPDATE_VALIDATE_ASSET_REMOTE_START',\n        UPDATE_VALIDATE_ASSET_REMOTE_END: 'UPDATE_VALIDATE_ASSET_REMOTE_END',\n        UPDATE_LOCAL_STORE_REMOTE_CACHE_DATASET_START:\n            'UPDATE_LOCAL_STORE_REMOTE_CACHE_DATASET_START',\n        UPDATE_LOCAL_STORE_REMOTE_CACHE_DATASET_END: 'UPDATE_LOCAL_STORE_REMOTE_CACHE_DATASET_END',\n    },\n    GET: {\n        ASSERTION_EXISTS_LOCAL_START: 'ASSERTION_EXISTS_LOCAL_START',\n        ASSERTION_EXISTS_LOCAL_END: 'ASSERTION_EXISTS_LOCAL_END',\n        GET_START: 'GET_START',\n        GET_INIT_START: 'GET_INIT_START',\n        GET_INIT_END: 'GET_INIT_END',\n        GET_VALIDATE_ASSET_START: 'GET_VALIDATE_ASSET_START',\n        GET_VALIDATE_ASSET_END: 'GET_VALIDATE_ASSET_END',\n        GET_LOCAL_START: 'GET_LOCAL_START',\n        GET_LOCAL_END: 'GET_LOCAL_END',\n        GET_REMOTE_START: 'GET_REMOTE_START',\n        GET_REMOTE_END: 'GET_REMOTE_END',\n        GET_FETCH_FROM_NODES_START: 'GET_FETCH_FROM_NODES_START',\n        GET_FETCH_FROM_NODES_END: 'GET_FETCH_FROM_NODES_END',\n        GET_FIND_NODES_START: 'GET_FIND_NODES_START',\n        GET_FIND_NODES_END: 'PUBLISH_FIND_NODES_END',\n        GET_END: 'GET_END',\n        GET_FAILED: 'GET_FAILED',\n    },\n    BATCH_GET: {\n        BATCH_GET_INIT: 'BATCH_GET_INIT',\n        BATCH_GET_START: 'BATCH_GET_START',\n        BATCH_GET_END: 'BATCH_GET_END',\n        BATCH_GET_FAILED: 'BATCH_GET_FAILED',\n        BATCH_GET_VALIDATE_ASSET_START: 'BATCH_GET_VALIDATE_ASSET_START',\n        BATCH_GET_VALIDATE_ASSET_END: 'BATCH_GET_VALIDATE_ASSET_END',\n        BATCH_GET_VALIDATE_ASSET_ERROR: 'BATCH_GET_VALIDATE_ASSET_ERROR',\n        BATCH_GET_LOCAL_START: 'BATCH_GET_LOCAL_START',\n        BATCH_GET_LOCAL_END: 'BATCH_GET_LOCAL_END',\n        BATCH_GET_REMOTE_START: 'BATCH_GET_REMOTE_START',\n        BATCH_GET_REMOTE_END: 'BATCH_GET_REMOTE_END',\n        BATCH_GET_REQUEST_REMOTE_ERROR: 'BATCH_GET_REQUEST_REMOTE_ERROR',\n        BATCH_GET_FIND_SHARD_START: 'BATCH_GET_FIND_SHARD_START',\n        BATCH_GET_FIND_SHARD_END: 'BATCH_GET_FIND_SHARD_END',\n    },\n    QUERY: {\n        QUERY_INIT_START: 'QUERY_INIT_START',\n        QUERY_INIT_END: 'QUERY_INIT_END',\n        QUERY_START: 'QUERY_START',\n        QUERY_END: 'QUERY_END',\n        QUERY_FAILED: 'QUERY_FAILED',\n    },\n    LOCAL_STORE: {\n        LOCAL_STORE_INIT_START: 'LOCAL_STORE_INIT_START',\n        LOCAL_STORE_INIT_END: 'LOCAL_STORE_INIT_END',\n        LOCAL_STORE_START: 'LOCAL_STORE_START',\n        LOCAL_STORE_END: 'LOCAL_STORE_END',\n        LOCAL_STORE_PROCESS_RESPONSE_START: 'LOCAL_STORE_PROCESS_RESPONSE_START',\n        LOCAL_STORE_PROCESS_RESPONSE_END: 'LOCAL_STORE_PROCESS_RESPONSE_END',\n    },\n    PARANET: {\n        PARANET_SYNC_START: 'PARANET_SYNC_START',\n        PARANET_SYNC_END: 'PARANET_SYNC_END',\n        PARANET_SYNC_MISSED_KAS_SYNC_START: 'PARANET_SYNC_MISSED_KAS_SYNC_START',\n        PARANET_SYNC_MISSED_KAS_SYNC_END: 'PARANET_SYNC_MISSED_KAS_SYNC_END',\n        PARANET_SYNC_NEW_KAS_SYNC_START: 'PARANET_SYNC_NEW_KAS_SYNC_START',\n        PARANET_SYNC_NEW_KAS_SYNC_END: 'PARANET_SYNC_NEW_KAS_SYNC_END',\n    },\n    ASK: {\n        ASK_START: 'ASK_START',\n        ASK_END: 'ASK_END',\n        ASK_REMOTE_START: 'ASK_REMOTE_START',\n        ASK_REMOTE_END: 'ASK_REMOTE_START',\n        ASK_FIND_NODES_START: 'ASK_FIND_NODES_START',\n        ASK_FIND_NODES_END: 'ASK_FIND_NODES_END',\n        ASK_FETCH_FROM_NODES_START: 'ASK_FETCH_FROM_NODES_START',\n        ASK_FETCH_FROM_NODES_END: 'ASK_FETCH_FROM_NODES_END',\n    },\n    FINALITY: {\n        FINALITY_START: 'FINALITY_START',\n        FINALITY_END: 'FINALITY_END',\n        FINALITY_REMOTE_START: 'FINALITY_REMOTE_START',\n        FINALITY_REMOTE_END: 'FINALITY_REMOTE_START',\n        FINALITY_REPLICATE_START: 'FINALITY_REPLICATE_START',\n        FINALITY_REPLICATE_END: 'FINALITY_REPLICATE_END',\n        FINALITY_FETCH_FROM_NODES_START: 'FINALITY_FETCH_FROM_NODES_START',\n        FINALITY_FETCH_FROM_NODES_END: 'FINALITY_FETCH_FROM_NODES_END',\n        PUBLISH_FINALITY_REMOTE_START: 'PUBLISH_FINALITY_REMOTE_START',\n        PUBLISH_FINALITY_REMOTE_END: 'PUBLISH_FINALITY_REMOTE_END',\n        PUBLISH_FINALITY_END: 'PUBLISH_FINALITY_END',\n        PUBLISH_FINALITY_FETCH_FROM_NODES_END: 'PUBLISH_FINALITY_FETCH_FROM_NODES_END',\n    },\n    SYNC: {\n        SYNC_START: 'SYNC_START',\n        SYNC_NEW_START: 'SYNC_NEW_START',\n        SYNC_MISSED_START: 'SYNC_MISSED_START',\n        SYNC_END: 'SYNC_END',\n        SYNC_NEW_END: 'SYNC_NEW_END',\n        SYNC_MISSED_END: 'SYNC_MISSED_END',\n        SYNC_PROGRESS_STATUS: 'SYNC_PROGRESS_STATUS',\n        SYNC_FAILED: 'SYNC_FAILED',\n        SYNC_NEW_FAILED: 'SYNC_NEW_FAILED',\n        SYNC_MISSED_FAILED: 'SYNC_MISSED_FAILED',\n    },\n};\n\nexport const OPERATIONS = {\n    PUBLISH: 'publish',\n    FINALITY: 'finality',\n    // UPDATE: 'update',\n    GET: 'get',\n    BATCH_GET: 'batchGet',\n    ASK: 'ask',\n};\n\nexport const SERVICE_AGREEMENT_START_TIME_DELAY_FOR_COMMITS_SECONDS = {\n    mainnet: 5 * 60,\n    testnet: 5 * 60,\n    devnet: 3 * 60,\n    test: 10,\n    development: 10,\n};\n\nexport const EXPECTED_TRANSACTION_ERRORS = {\n    INSUFFICIENT_FUNDS: 'InsufficientFunds',\n    NODE_ALREADY_SUBMITTED_COMMIT: 'NodeAlreadySubmittedCommit',\n    TIMEOUT_EXCEEDED: 'timeout exceeded',\n    TOO_LOW_PRIORITY: 'TooLowPriority',\n    NODE_ALREADY_REWARDED: 'NodeAlreadyRewarded',\n    SERVICE_AGREEMENT_DOESNT_EXIST: 'ServiceAgreementDoesntExist',\n    INVALID_SCORE_FUNCTION_ID: 'InvalidScoreFunctionId',\n    COMMIT_WINDOW_CLOSED: 'CommitWindowClosed',\n    NODE_NOT_IN_SHARDING_TABLE: 'NodeNotInShardingTable',\n    PROOF_WINDOW_CLOSED: 'ProofWindowClosed',\n    NODE_NOT_AWARDED: 'NodeNotAwarded',\n    WRONG_MERKLE_PROOF: 'WrongMerkleProof',\n    NO_MINTED_ASSETS: 'NoMintedAssets',\n    NONCE_TOO_LOW: 'nonce too low',\n    REPLACEMENT_UNDERPRICED: 'replacement transaction underpriced',\n    ALREADY_KNOWN: 'already known',\n    EXECUTION_FAILED: 'transaction execution fails',\n    FEE_TOO_LOW: 'feetoolow',\n    SOCKET_HANG_UP: 'socket hang up',\n    ECONNRESET: 'econnreset',\n    ECONNREFUSED: 'econnrefused',\n    SERVER_ERROR: 'server error',\n    BAD_GATEWAY: '502',\n    SERVICE_UNAVAILABLE: '503',\n    EXPECT_BLOCK_NUMBER: 'expect block number from id',\n};\n\n/**\n * @constant {number} OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS -\n * operation id command cleanup interval time 24h\n */\nexport const OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS = 24 * 60 * 60 * 1000;\n/**\n * @constant {number} OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS -\n * operation id memory cleanup interval time 1h\n */\nexport const OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS = 60 * 60 * 1000;\n/**\n * @constant {number} FINALIZED_COMMAND_CLEANUP_TIME_MILLS - Command cleanup interval time\n * finalized commands command cleanup interval time 24h\n */\n\nexport const PUBLISH_STORAGE_MEMORY_CLEANUP_COMMAND_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const PUBLISH_STORAGE_FILE_CLEANUP_COMMAND_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\n\nexport const FINALIZED_COMMAND_CLEANUP_TIME_MILLS = 1 * 60 * 60 * 1000;\nexport const FINALIZED_COMMAND_CLEANUP_TIME_DELAY = 1 * 60 * 60 * 1000;\n\nexport const GET_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const GET_CLEANUP_TIME_DELAY = 24 * 60 * 60 * 1000;\nexport const GET_RESPONSE_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const GET_RESPONSE_CLEANUP_TIME_DELAY = 24 * 60 * 60 * 1000;\n\nexport const PUBLISH_CLEANUP_TIME_MILLS = 1 * 60 * 60 * 1000;\nexport const PUBLISH_CLEANUP_TIME_DELAY = 1 * 60 * 60 * 1000;\nexport const PUBLISH_RESPONSE_CLEANUP_TIME_MILLS = 1 * 60 * 60 * 1000;\nexport const PUBLISH_RESPONSE_CLEANUP_TIME_DELAY = 1 * 60 * 60 * 1000;\n\nexport const UPDATE_CLEANUP_TIME_MILLS = 1 * 60 * 60 * 1000;\nexport const UPDATE_CLEANUP_TIME_DELAY = 1 * 60 * 60 * 1000;\nexport const UPDATE_RESPONSE_CLEANUP_TIME_MILLS = 1 * 60 * 60 * 1000;\nexport const UPDATE_RESPONSE_CLEANUP_TIME_DELAY = 1 * 60 * 60 * 1000;\n\nexport const ASK_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const ASK_CLEANUP_TIME_DELAY = 24 * 60 * 60 * 1000;\nexport const ASK_RESPONSE_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const ASK_RESPONSE_CLEANUP_TIME_DELAY = 24 * 60 * 60 * 1000;\n\nexport const FINALITY_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const FINALITY_CLEANUP_TIME_DELAY = 24 * 60 * 60 * 1000;\nexport const FINALITY_RESPONSE_CLEANUP_TIME_MILLS = 12 * 60 * 60 * 1000;\nexport const FINALITY_RESPONSE_CLEANUP_TIME_DELAY = 24 * 60 * 60 * 1000;\n\nexport const PROCESSED_BLOCKCHAIN_EVENTS_CLEANUP_TIME_MILLS = 1 * 60 * 60 * 1000;\nexport const PROCESSED_BLOCKCHAIN_EVENTS_CLEANUP_TIME_DELAY = 1 * 60 * 60 * 1000;\n\n/**\n * @constant {number} COMMAND_STATUS -\n * Status for commands\n */\nexport const COMMAND_STATUS = {\n    FAILED: 'FAILED',\n    EXPIRED: 'EXPIRED',\n    UNKNOWN: 'UNKNOWN',\n    STARTED: 'STARTED',\n    PENDING: 'PENDING',\n    COMPLETED: 'COMPLETED',\n    REPEATING: 'REPEATING',\n};\n\nexport const PENDING_STORAGE_FILES_FOR_REMOVAL_MAX_NUMBER = 100_000;\n\nexport const OPERATION_ID_FILES_FOR_REMOVAL_MAX_NUMBER = 100;\n\nexport const REPOSITORY_ROWS_FOR_REMOVAL_MAX_NUMBER = 50_000;\n\nexport const MIGRATION_FOLDER = 'migrations';\n\nexport const PUBLISHER_NODE_SIGNATURES_FOLDER = 'publisher';\n\nexport const NETWORK_SIGNATURES_FOLDER = 'network';\n\n/**\n * How many commands will run in parallel\n * @type {number}\n */\nexport const GENERAL_COMMAND_QUEUE_PARALLELISM = 100;\n\nexport const BATCH_GET_COMMAND_QUEUE_PARALLELISM = 20;\n\nexport const GET_LATEST_SERVICE_AGREEMENT_BATCH_SIZE = 50;\n\nexport const GET_ASSERTION_IDS_MAX_RETRY_COUNT = 5;\n\nexport const GET_ASSERTION_IDS_RETRY_DELAY_IN_SECONDS = 2;\n\nexport const GET_LATEST_SERVICE_AGREEMENT_EXCLUDE_LATEST_TOKEN_ID = 1;\n\n/**\n * @constant {object} HTTP_API_ROUTES -\n *  HTTP API Routes with parameters\n */\nexport const HTTP_API_ROUTES = {\n    v0: {\n        // publish: {\n        //     method: 'post',\n        //     path: '/publish',\n        //     options: { rateLimit: true },\n        // },\n        // update: {\n        //     method: 'post',\n        //     path: '/update',\n        //     options: { rateLimit: true },\n        // },\n        query: {\n            method: 'post',\n            path: '/query',\n            options: {},\n        },\n        // 'local-store': {\n        //     method: 'post',\n        //     path: '/local-store',\n        //     options: {},\n        // },\n        get: {\n            method: 'post',\n            path: '/get',\n            options: { rateLimit: true },\n        },\n        result: {\n            method: 'get',\n            path: '/:operation/:operationId',\n            options: {},\n        },\n        info: {\n            method: 'get',\n            path: '/info',\n            options: {},\n        },\n        'bid-suggestion': {\n            method: 'get',\n            path: '/bid-suggestion',\n            options: {},\n        },\n    },\n    v1: {\n        publish: {\n            method: 'post',\n            path: '/publish',\n            options: { rateLimit: true },\n        },\n        finality: {\n            method: 'get',\n            path: '/finality',\n            options: {},\n        },\n        // update: {\n        //     method: 'post',\n        //     path: '/update',\n        //     options: { rateLimit: true },\n        // },\n        query: {\n            method: 'post',\n            path: '/query',\n            options: {},\n        },\n        get: {\n            method: 'post',\n            path: '/get',\n            options: { rateLimit: true },\n        },\n        result: {\n            method: 'get',\n            path: '/:operation/:operationId',\n            options: {},\n        },\n        info: {\n            method: 'get',\n            path: '/info',\n            options: {},\n        },\n        ask: {\n            method: 'post',\n            path: '/ask',\n            options: {},\n        },\n        'direct-query': {\n            method: 'post',\n            path: '/direct-query',\n            options: {},\n        },\n        'local-store': {\n            method: 'post',\n            path: '/local-store',\n            options: {},\n        },\n    },\n};\n\n/**\n * @constant {object} NETWORK_PROTOCOLS -\n *  Network protocols\n */\nexport const NETWORK_PROTOCOLS = {\n    STORE: ['/store/1.0.0'],\n    // UPDATE: ['/update/1.0.0'],\n    GET: ['/get/1.0.0'],\n    BATCH_GET: ['/batch-get/1.0.0'],\n    ASK: ['/ask/1.0.0'],\n    FINALITY: ['/finality/1.0.0'],\n};\n\nexport const OPERATION_STATUS = {\n    IN_PROGRESS: 'IN_PROGRESS',\n    FAILED: 'FAILED',\n    COMPLETED: 'COMPLETED',\n};\n\nexport const AGREEMENT_STATUS = {\n    ACTIVE: 'ACTIVE',\n    EXPIRED: 'EXPIRED',\n};\n\nexport const OPERATION_REQUEST_STATUS = {\n    FAILED: 'FAILED',\n    COMPLETED: 'COMPLETED',\n};\n\n/**\n * Local query types\n * @type {{CONSTRUCT: string, SELECT: string}}\n */\nexport const QUERY_TYPES = {\n    SELECT: 'SELECT',\n    CONSTRUCT: 'CONSTRUCT',\n};\n\n/**\n * Local store types\n * @type {{TRIPLE: string, PENDING: string}}\n */\nexport const LOCAL_STORE_TYPES = {\n    TRIPLE: 'TRIPLE',\n    TRIPLE_PARANET: 'TRIPLE_PARANET',\n};\n\n/**\n * Contract names\n * @type {{SHARDING_TABLE: string}}\n */\nexport const CONTRACTS = {\n    SHARDING_TABLE: 'ShardingTable',\n    STAKING: 'Staking',\n    PROFILE: 'Profile',\n    HUB: 'Hub',\n    PARAMETERS_STORAGE: 'ParametersStorage',\n    IDENTITY_STORAGE: 'IdentityStorage',\n    LOG2PLDSF: 'Log2PLDSF',\n    LINEAR_SUM: 'LinearSum',\n    PARANETS_REGISTRY: 'ParanetsRegistry',\n};\n\nexport const MONITORED_CONTRACT_EVENTS = {\n    Hub: ['NewContract', 'ContractChanged', 'NewAssetStorage', 'AssetStorageChanged'],\n    ParametersStorage: ['ParameterChanged'],\n    KnowledgeCollectionStorage: ['KnowledgeCollectionCreated'],\n};\n\nexport const MONITORED_CONTRACTS = Object.keys(MONITORED_CONTRACT_EVENTS);\n\nexport const MONITORED_EVENTS = Object.values(MONITORED_CONTRACT_EVENTS).flatMap(\n    (events) => events,\n);\n\nexport const CONTRACT_INDEPENDENT_EVENTS = {};\n\nexport const NODE_ENVIRONMENTS = {\n    DEVELOPMENT: 'development',\n    TEST: 'test',\n    DEVNET: 'devnet',\n    TESTNET: 'testnet',\n    MAINNET: 'mainnet',\n};\n\nexport const MAXIMUM_FETCH_EVENTS_FAILED_COUNT = 1000;\n\nexport const CONTRACT_EVENT_FETCH_INTERVALS = {\n    MAINNET: 10 * 1000,\n    DEVELOPMENT: 4 * 1000,\n};\n\nexport const TRANSACTION_CONFIRMATIONS = 1;\n\nexport const SERVICE_AGREEMENT_SOURCES = {\n    BLOCKCHAIN: 'blockchain',\n    EVENT: 'event',\n    CLIENT: 'client',\n    NODE: 'node',\n};\n\nexport const CACHE_DATA_TYPES = {\n    NUMBER: 'number',\n    ANY: 'any',\n};\n\nexport const PARANET_SYNC_SOURCES = {\n    SYNC: 'sync',\n    LOCAL_STORE: 'local_store',\n};\n\n/**\n * CACHED_FUNCTIONS:\n * ContractName: {\n *     functionName: returnType\n * }\n * @type {{IdentityStorageContract: [{name: string, type: string}], ParametersStorageContract: *}}\n */\nexport const CACHED_FUNCTIONS = {\n    ParametersStorageContract: {\n        r0: CACHE_DATA_TYPES.NUMBER,\n        r1: CACHE_DATA_TYPES.NUMBER,\n        r2: CACHE_DATA_TYPES.NUMBER,\n        finalizationCommitsNumber: CACHE_DATA_TYPES.NUMBER,\n        updateCommitWindowDuration: CACHE_DATA_TYPES.NUMBER,\n        commitWindowDurationPerc: CACHE_DATA_TYPES.NUMBER,\n        proofWindowDurationPerc: CACHE_DATA_TYPES.NUMBER,\n        epochLength: CACHE_DATA_TYPES.NUMBER,\n        minimumStake: CACHE_DATA_TYPES.ANY,\n        maximumStake: CACHE_DATA_TYPES.ANY,\n        minProofWindowOffsetPerc: CACHE_DATA_TYPES.NUMBER,\n        maxProofWindowOffsetPerc: CACHE_DATA_TYPES.NUMBER,\n    },\n    IdentityStorageContract: {\n        getIdentityId: CACHE_DATA_TYPES.NUMBER,\n    },\n};\n\nexport const LOW_BID_SUGGESTION = 'low';\nexport const MED_BID_SUGGESTION = 'med';\nexport const HIGH_BID_SUGGESTION = 'high';\nexport const ALL_BID_SUGGESTION = 'all';\nexport const BID_SUGGESTION_RANGE_ENUM = [\n    LOW_BID_SUGGESTION,\n    MED_BID_SUGGESTION,\n    HIGH_BID_SUGGESTION,\n    ALL_BID_SUGGESTION,\n];\nexport const LOW_BID_SUGGESTION_OFFSET = 9;\nexport const MED_BID_SUGGESTION_OFFSET = 11;\nexport const HIGH_BID_SUGGESTION_OFFSET = 14;\n\nexport const LOCAL_INSERT_FOR_ASSET_SYNC_MAX_ATTEMPTS = 5;\nexport const LOCAL_INSERT_FOR_ASSET_SYNC_RETRY_DELAY = 1000;\n\nexport const LOCAL_INSERT_FOR_CURATED_PARANET_MAX_ATTEMPTS = 5;\nexport const LOCAL_INSERT_FOR_CURATED_PARANET_RETRY_DELAY = 1000;\n\nexport const MAX_RETRIES_READ_CACHED_PUBLISH_DATA = 5;\nexport const RETRY_DELAY_READ_CACHED_PUBLISH_DATA = 5 * 1000;\n\nexport const TRIPLE_STORE_REPOSITORY = {\n    DKG: 'dkg',\n    DKG_HISTORIC: 'dkg-historic',\n};\n\nexport const TRIPLES_VISIBILITY = {\n    PUBLIC: 'public',\n    PRIVATE: 'private',\n    ALL: 'all',\n};\n\nexport const V6_CONTENT_STORAGE_MAP = {\n    BASE_MAINNET: '0x3bdfA81079B2bA53a25a6641608E5E1E6c464597',\n    BASE_TESTNET: '0x9e3071Dc0730CB6dd0ce42969396D716Ea33E7e1',\n    BASE_DEVNET: '0xBe08A25dcF2B68af88501611e5456571f50327B4',\n    GNOSIS_MAINNET: '0xf81a8C0008DE2DCdb73366Cf78F2b178616d11DD',\n    GNOSIS_TESTNET: '0xeA3423e02c8d231532dab1BCE5D034f3737B3638',\n    GNOSIS_DEVNET: '0x3db64dD0Ac054610d1e2Af9Cca0fbCB1A7f4C2d8',\n    OTP_MAINNET: '0x5cAC41237127F94c2D21dAe0b14bFeFa99880630',\n    OTP_TESTNET: '0x1A061136Ed9f5eD69395f18961a0a535EF4B3E5f',\n    OTP_DEVNET: '0xABd59A9aa71847F499d624c492d3903dA953d67a',\n};\n\nexport const PROOFING_INTERVAL = 5 * 60 * 1000;\nexport const PROOFING_MAX_ATTEMPTS = 120;\nexport const REORG_PROOFING_BUFFER = 60 * 1000;\nexport const CHUNK_SIZE = 32;\n\nexport const CLAIM_REWARDS_INTERVAL = 60 * 60 * 1000;\nexport const CLAIM_REWARDS_BATCH_SIZE = 10;\n\nexport const BATCH_GET_BATCH_SIZE = 2;\nexport const BATCH_GET_UAL_MAX_LIMIT = 1000;\n\nexport const SYNC_INTERVAL = 12 * 1000;\nexport const SYNC_BATCH_GET_WAIT_TIME = 1000;\nexport const SYNC_BATCH_GET_MAX_ATTEMPTS = 15 * 60;\n\nexport const MAX_TOKEN_ID_PER_GET_PAGE = 50;\n\nexport const BLAZEGRAPH_HEALTH_INTERVAL = 60 * 1000;\n\nexport const MAX_COMMAND_LIFETIME = 15 * 60 * 1000;\n"
  },
  {
    "path": "src/controllers/http-api/base-http-api-controller.js",
    "content": "class BaseController {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n    }\n\n    returnResponse(res, status, data) {\n        res.status(status).send(data);\n    }\n}\n\nexport default BaseController;\n"
  },
  {
    "path": "src/controllers/http-api/http-api-router.js",
    "content": "import stringUtil from '../../service/util/string-util.js';\nimport { HTTP_API_ROUTES } from '../../constants/constants.js';\n\nclass HttpApiRouter {\n    constructor(ctx) {\n        this.httpClientModuleManager = ctx.httpClientModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n\n        this.apiRoutes = HTTP_API_ROUTES;\n        this.apiVersions = Object.keys(this.apiRoutes);\n\n        this.routers = {};\n        for (const version of this.apiVersions) {\n            this.routers[version] = this.httpClientModuleManager.createRouterInstance();\n\n            const operations = Object.keys(this.apiRoutes[version]);\n\n            for (const operation of operations) {\n                const versionedController = `${stringUtil.toCamelCase(\n                    operation,\n                )}HttpApiController${stringUtil.capitalize(version)}`;\n\n                this[versionedController] = ctx[versionedController];\n            }\n        }\n        this.routers.latest = this.httpClientModuleManager.createRouterInstance();\n\n        this.jsonSchemaService = ctx.jsonSchemaService;\n    }\n\n    async initialize() {\n        this.initializeBeforeMiddlewares();\n        await this.initializeVersionedListeners();\n        this.initializeRouters();\n        this.initializeAfterMiddlewares();\n        await this.httpClientModuleManager.listen();\n    }\n\n    initializeBeforeMiddlewares() {\n        const blockchainImplementations = this.blockchainModuleManager.getImplementationNames();\n        this.httpClientModuleManager.initializeBeforeMiddlewares(blockchainImplementations);\n    }\n\n    async initializeVersionedListeners() {\n        const descendingOrderedVersions = this.apiVersions.sort((a, b) => b.localeCompare(a));\n        const mountedLatestRoutes = new Set();\n\n        for (const version of descendingOrderedVersions) {\n            for (const [name, route] of Object.entries(this.apiRoutes[version])) {\n                const { method, path, options } = route;\n                const camelRouteName = stringUtil.toCamelCase(name);\n                const controller = `${camelRouteName}HttpApiController${stringUtil.capitalize(\n                    version,\n                )}`;\n                const schema = `${camelRouteName}Schema`;\n\n                if (\n                    schema in this.jsonSchemaService &&\n                    typeof this.jsonSchemaService[schema] === 'function'\n                ) {\n                    // eslint-disable-next-line no-await-in-loop\n                    options.requestSchema = await this.jsonSchemaService[schema](version);\n                }\n\n                const middlewares = this.httpClientModuleManager.selectMiddlewares(options);\n                const callback = (req, res) => {\n                    this[controller].handleRequest(req, res);\n                };\n\n                this.routers[version][method](path, ...middlewares, callback);\n\n                if (!mountedLatestRoutes.has(route.name)) {\n                    this.routers.latest[method](path, ...middlewares, callback);\n                    mountedLatestRoutes.add(route.name);\n                }\n            }\n        }\n    }\n\n    initializeRouters() {\n        for (const version of this.apiVersions) {\n            this.httpClientModuleManager.use(`/${version}`, this.routers[version]);\n        }\n\n        this.httpClientModuleManager.use('/latest', this.routers.latest);\n        this.httpClientModuleManager.use('/', this.routers.v0);\n    }\n\n    initializeAfterMiddlewares() {\n        this.httpClientModuleManager.initializeAfterMiddlewares();\n    }\n}\n\nexport default HttpApiRouter;\n"
  },
  {
    "path": "src/controllers/http-api/v0/bid-suggestion-http-api-controller-v0.js",
    "content": "import { BigNumber } from 'ethers';\nimport BaseController from '../base-http-api-controller.js';\nimport { ONE_ETHER } from '../../../constants/constants.js';\n\nclass BidSuggestionController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async handleRequest(req, res) {\n        try {\n            const { blockchain, epochsNumber, assertionSize } = req.query;\n            const promises = [\n                this.blockchainModuleManager.getTimeUntilNextEpoch(blockchain),\n                this.blockchainModuleManager.getEpochLength(blockchain),\n                this.blockchainModuleManager.getStakeWeightedAverageAsk(blockchain),\n            ];\n            const [timeUntilNextEpoch, epochLength, stakeWeightedAverageAsk] = await Promise.all(\n                promises,\n            );\n            const timeUntilNextEpochScaled = BigNumber.from(timeUntilNextEpoch)\n                .mul(ONE_ETHER)\n                .div(BigNumber.from(epochLength));\n            const epochsNumberScaled = BigNumber.from(epochsNumber).mul(ONE_ETHER);\n            const storageTime = timeUntilNextEpochScaled.add(epochsNumberScaled);\n            const bidSuggestion = BigNumber.from(stakeWeightedAverageAsk)\n                .mul(storageTime)\n                .mul(BigNumber.from(assertionSize))\n                .div(ONE_ETHER);\n            this.returnResponse(res, 200, { bidSuggestion: bidSuggestion.toString() });\n        } catch (error) {\n            this.logger.error(`Unable to get bid suggestion. Error: ${error}`);\n            this.returnResponse(res, 500, {\n                code: 500,\n                message: 'Unable to calculate bid suggestion',\n            });\n        }\n    }\n}\n\nexport default BidSuggestionController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/get-http-api-controller-v0.js",
    "content": "import {\n    OPERATION_ID_STATUS,\n    OPERATION_STATUS,\n    ERROR_TYPE,\n    TRIPLES_VISIBILITY,\n    V6_CONTENT_STORAGE_MAP,\n} from '../../../constants/constants.js';\nimport BaseController from '../base-http-api-controller.js';\n\nclass GetController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.operationService = ctx.getService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.ualService = ctx.ualService;\n        this.validationService = ctx.validationService;\n        this.fileService = ctx.fileService;\n    }\n\n    async handleRequest(req, res) {\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.GET.GET_START,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            null,\n            OPERATION_ID_STATUS.GET.GET_INIT_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        await this.repositoryModuleManager.createOperationRecord(\n            this.operationService.getOperationName(),\n            operationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n\n        let tripleStoreMigrationAlreadyExecuted = false;\n        try {\n            tripleStoreMigrationAlreadyExecuted =\n                (await this.fileService.readFile(\n                    '/root/ot-node/data/migrations/v8DataMigration',\n                )) === 'MIGRATED';\n        } catch (e) {\n            this.logger.warn(`No triple store migration file error: ${e}`);\n        }\n        let blockchain;\n        let contract;\n        let knowledgeCollectionId;\n        let knowledgeAssetId;\n        try {\n            const { paranetUAL, includeMetadata, contentType } = req.body;\n            let { id } = req.body;\n            ({ blockchain, contract, knowledgeCollectionId, knowledgeAssetId } =\n                this.ualService.resolveUAL(id));\n            contract = contract.toLowerCase();\n            id = this.ualService.deriveUAL(blockchain, contract, knowledgeCollectionId);\n            this.logger.info(`Get for ${id} with operation id ${operationId} initiated.`);\n\n            // Get assertionId - datasetRoot\n            //\n\n            const isV6Contract = Object.values(V6_CONTENT_STORAGE_MAP).some((ca) =>\n                ca.toLowerCase().includes(contract.toLowerCase()),\n            );\n\n            const commandSequence = [];\n\n            if (!tripleStoreMigrationAlreadyExecuted && isV6Contract) {\n                commandSequence.push('getAssertionMerkleRootCommand');\n            }\n\n            commandSequence.push('getFindShardCommand');\n\n            await this.commandExecutor.add({\n                name: commandSequence[0],\n                sequence: commandSequence.slice(1),\n                delay: 0,\n                data: {\n                    ual: id,\n                    includeMetadata,\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                    knowledgeAssetId,\n                    operationId,\n                    paranetUAL,\n                    isV6Contract,\n                    contentType: contentType ?? TRIPLES_VISIBILITY.ALL,\n                    isOperationV0: true,\n                },\n                transactional: false,\n            });\n\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                blockchain,\n                OPERATION_ID_STATUS.GET.GET_INIT_END,\n            );\n        } catch (error) {\n            this.logger.error(`Error while initializing get data: ${error.message}.`);\n\n            await this.operationService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Unable to get data, Failed to process input data!',\n                ERROR_TYPE.GET.GET_ROUTE_ERROR,\n            );\n        }\n    }\n}\n\nexport default GetController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/info-http-api-controller-v0.js",
    "content": "import { createRequire } from 'module';\nimport BaseController from '../base-http-api-controller.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../../../../package.json');\n\nclass InfoController extends BaseController {\n    handleRequest(_, res) {\n        this.returnResponse(res, 200, {\n            version,\n        });\n    }\n}\n\nexport default InfoController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/local-store-http-api-controller-v0.js",
    "content": "import BaseController from '../base-http-api-controller.js';\nimport { OPERATION_ID_STATUS } from '../../../constants/constants.js';\n\nclass LocalStoreController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.dataService = ctx.dataService;\n        this.fileService = ctx.fileService;\n    }\n\n    async handleRequest(req, res) {\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.LOCAL_STORE.LOCAL_STORE_INIT_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            null,\n            OPERATION_ID_STATUS.LOCAL_STORE.LOCAL_STORE_INIT_END,\n        );\n        let assertions;\n        const { filePath } = req.body;\n        if (filePath) {\n            assertions = JSON.parse(await this.fileService.readFile(filePath));\n        } else {\n            assertions = req.body;\n        }\n\n        const cachedAssertions = {\n            public: {},\n            private: {},\n        };\n        switch (assertions.length) {\n            case 1: {\n                const { assertion, assertionId } = assertions[0];\n                cachedAssertions.public = { assertion, assertionId };\n\n                break;\n            }\n            case 2: {\n                const isFirstPublic =\n                    this.dataService.getPrivateAssertionId(assertions[0].assertion) != null;\n\n                const publicAssertionData = isFirstPublic ? assertions[0] : assertions[1];\n                const privateAssertionData = isFirstPublic ? assertions[1] : assertions[0];\n\n                cachedAssertions.public = {\n                    assertion: publicAssertionData.assertion,\n                    assertionId: publicAssertionData.assertionId,\n                };\n                cachedAssertions.private = {\n                    assertion: privateAssertionData.assertion,\n                    assertionId: privateAssertionData.assertionId,\n                };\n\n                break;\n            }\n            default:\n                throw Error('Unexpected number of assertions in local store');\n        }\n\n        this.logger.info(\n            `Received assertion with assertion ids: ${assertions.map(\n                (reqObject) => reqObject.assertionId,\n            )}. Operation id: ${operationId}`,\n        );\n\n        await this.operationIdService.cacheOperationIdDataToMemory(operationId, cachedAssertions);\n\n        await this.operationIdService.cacheOperationIdDataToFile(operationId, cachedAssertions);\n\n        const commandSequence = ['localStoreCommand'];\n\n        await this.commandExecutor.add({\n            name: commandSequence[0],\n            sequence: commandSequence.slice(1),\n            delay: 0,\n            data: {\n                operationId,\n                blockchain: assertions[0].blockchain,\n                contract: assertions[0].contract,\n                tokenId: assertions[0].tokenId,\n                storeType: assertions[0].storeType,\n                paranetUAL: assertions[0].paranetUAL,\n                isOperationV0: true,\n            },\n            transactional: false,\n        });\n    }\n}\n\nexport default LocalStoreController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/publish-http-api-controller-v0.js",
    "content": "import BaseController from '../base-http-api-controller.js';\nimport {\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    OPERATION_STATUS,\n    LOCAL_STORE_TYPES,\n} from '../../../constants/constants.js';\n\nclass PublishController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationService = ctx.publishService;\n        this.operationIdService = ctx.operationIdService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager; // this is not used\n        this.pendingStorageService = ctx.pendingStorageService;\n    }\n\n    async handleRequest(req, res) {\n        const {\n            assertion: dataset,\n            assertionId: datasetRoot,\n            blockchain,\n            contract,\n            tokenId,\n        } = req.body;\n\n        this.logger.info(\n            `Received asset with dataset root: ${datasetRoot}, blockchain: ${blockchain}`,\n        );\n\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_START,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_INIT_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_INIT_END,\n        );\n        await this.repositoryModuleManager.createOperationRecord(\n            this.operationService.getOperationName(),\n            operationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n\n        try {\n            await this.operationIdService.cacheOperationIdDataToMemory(operationId, {\n                dataset,\n                datasetRoot,\n            });\n\n            await this.operationIdService.cacheOperationIdDataToFile(operationId, {\n                dataset,\n                datasetRoot,\n            });\n\n            await this.pendingStorageService.cacheDataset(operationId, datasetRoot, dataset);\n\n            const commandSequence = ['publishFindShardCommand'];\n\n            await this.commandExecutor.add({\n                name: commandSequence[0],\n                sequence: commandSequence.slice(1),\n                delay: 0,\n                period: 5000,\n                retries: 3,\n                data: {\n                    datasetRoot,\n                    blockchain,\n                    operationId,\n                    contract,\n                    tokenId,\n                    isOperationV0: true,\n                    storeType: LOCAL_STORE_TYPES.TRIPLE,\n                },\n                transactional: false,\n            });\n        } catch (error) {\n            this.logger.error(\n                `Error while initializing publish data: ${error.message}. ${error.stack}`,\n            );\n\n            await this.operationService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Unable to publish data, Failed to process input data!',\n                ERROR_TYPE.PUBLISH.PUBLISH_ROUTE_ERROR,\n            );\n        }\n    }\n}\n\nexport default PublishController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/query-http-api-controller-v0.js",
    "content": "import BaseController from '../base-http-api-controller.js';\n\nimport { OPERATION_ID_STATUS, TRIPLE_STORE_REPOSITORIES } from '../../../constants/constants.js';\n\nclass QueryController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.fileService = ctx.fileService;\n    }\n\n    async handleRequest(req, res) {\n        const { query, type: queryType, repository } = req.body;\n\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.QUERY.QUERY_INIT_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        let tripleStoreMigrationAlreadyExecuted = false;\n        try {\n            tripleStoreMigrationAlreadyExecuted =\n                (await this.fileService.readFile(\n                    '/root/ot-node/data/migrations/v8DataMigration',\n                )) === 'MIGRATED';\n        } catch (e) {\n            this.logger.warn(`No triple store migration file error: ${e}`);\n        }\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            null,\n            OPERATION_ID_STATUS.QUERY.QUERY_INIT_END,\n        );\n\n        await this.commandExecutor.add({\n            name: 'queryCommand',\n            sequence: [],\n            delay: 0,\n            data: {\n                query,\n                queryType,\n                repository:\n                    !tripleStoreMigrationAlreadyExecuted && repository\n                        ? [repository, TRIPLE_STORE_REPOSITORIES.DKG]\n                        : TRIPLE_STORE_REPOSITORIES.DKG,\n                operationId,\n            },\n            transactional: false,\n        });\n    }\n}\n\nexport default QueryController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/request-schema/bid-suggestion-schema-v0.js",
    "content": "import { BID_SUGGESTION_RANGE_ENUM } from '../../../../constants/constants.js';\n\nexport default (argumentsObject) => ({\n    type: 'object',\n    required: [\n        'blockchain',\n        'epochsNumber',\n        'assertionSize',\n        'contentAssetStorageAddress',\n        'firstAssertionId',\n        'hashFunctionId',\n    ],\n    properties: {\n        blockchain: {\n            enum: argumentsObject.blockchainImplementationNames,\n        },\n        epochsNumber: {\n            type: 'number',\n            minimum: 1,\n        },\n        assertionSize: {\n            type: 'number',\n            minimum: 1,\n        },\n        contentAssetStorageAddress: {\n            type: 'string',\n            minLength: 42,\n            maxLength: 42,\n        },\n        firstAssertionId: {\n            type: 'string',\n            minLength: 66,\n            maxLength: 66,\n        },\n        hashFunctionId: {\n            type: 'number',\n            minimum: 1,\n            maximum: 1,\n        },\n        proximityScoreFunctionsPairId: {\n            type: 'number',\n            minimum: 1,\n            maximum: 2,\n        },\n        bidSuggestionRange: {\n            type: 'string',\n            enum: BID_SUGGESTION_RANGE_ENUM,\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v0/request-schema/get-schema-v0.js",
    "content": "export default () => ({\n    type: 'object',\n    required: ['id'],\n    properties: {\n        id: {\n            type: 'string',\n        },\n        contentType: {\n            type: 'string',\n        },\n        includeMetadata: {\n            type: 'boolean',\n        },\n        hashFunctionId: {\n            type: 'number',\n            minimum: 1,\n        },\n        paranetUAL: {\n            type: ['string', 'null'],\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v0/request-schema/local-store-schema-v0.js",
    "content": "import { LOCAL_STORE_TYPES } from '../../../../constants/constants.js';\n\nexport default (argumentsObject) => ({\n    type: ['object', 'array'],\n    items: {\n        oneOf: [\n            {\n                type: 'object',\n                required: ['assertionId', 'assertion'],\n                properties: {\n                    assertionId: {\n                        type: 'string',\n                        minLength: 66,\n                        maxLength: 66,\n                    },\n                    assertion: {\n                        type: 'array',\n                        items: {\n                            type: 'string',\n                        },\n                        minItems: 1,\n                    },\n                    blockchain: {\n                        enum: argumentsObject.blockchainImplementationNames,\n                    },\n                    contract: {\n                        type: 'string',\n                        minLength: 42,\n                        maxLength: 42,\n                    },\n                    tokenId: {\n                        type: 'number',\n                        minimum: 0,\n                    },\n                    storeType: {\n                        enum: [LOCAL_STORE_TYPES.TRIPLE, LOCAL_STORE_TYPES.TRIPLE_PARANET],\n                    },\n                    paranetUAL: {\n                        type: 'string',\n                    },\n                },\n                minItems: 1,\n                maxItems: 2,\n            },\n            {\n                type: 'object',\n                required: ['filePath'],\n                properties: {\n                    filePath: {\n                        type: 'string',\n                    },\n                    paranetUAL: {\n                        type: 'string',\n                    },\n                    blockchain: {\n                        enum: argumentsObject.blockchainImplementationNames,\n                    },\n                    contract: {\n                        type: 'string',\n                        minLength: 42,\n                        maxLength: 42,\n                    },\n                    tokenId: {\n                        type: 'number',\n                        minimum: 0,\n                    },\n                    storeType: {\n                        enum: [LOCAL_STORE_TYPES.TRIPLE, LOCAL_STORE_TYPES.TRIPLE_PARANET],\n                    },\n                },\n            },\n        ],\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v0/request-schema/publish-schema-v0.js",
    "content": "export default (argumentsObject) => ({\n    type: 'object',\n    required: ['assertionId', 'assertion', 'blockchain', 'contract', 'tokenId'],\n    properties: {\n        assertionId: {\n            type: 'string',\n            minLength: 66,\n            maxLength: 66,\n        },\n        assertion: {\n            type: 'array',\n            items: {\n                type: 'string',\n            },\n            minItems: 1,\n        },\n        blockchain: {\n            enum: argumentsObject.blockchainImplementationNames,\n        },\n        contract: {\n            type: 'string',\n            minLength: 42,\n            maxLength: 42,\n        },\n        tokenId: {\n            type: 'number',\n            minimum: 0,\n        },\n        hashFunctionId: {\n            type: 'number',\n            minimum: 1,\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v0/request-schema/query-schema-v0.js",
    "content": "import { QUERY_TYPES } from '../../../../constants/constants.js';\n\nexport default () => ({\n    type: 'object',\n    required: ['type', 'query'],\n    properties: {\n        type: {\n            enum: [QUERY_TYPES.CONSTRUCT, QUERY_TYPES.SELECT],\n        },\n        query: {\n            type: 'string',\n        },\n        // repository: {\n        //     type: 'string',\n        // },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v0/request-schema/update-schema-v0.js",
    "content": "export default (argumentsObject) => ({\n    type: 'object',\n    required: ['assertionId', 'assertion', 'blockchain', 'contract', 'tokenId'],\n    properties: {\n        assertionId: {\n            type: 'string',\n            minLength: 66,\n            maxLength: 66,\n        },\n        assertion: {\n            type: 'array',\n            items: {\n                type: 'string',\n            },\n            minItems: 1,\n        },\n        blockchain: {\n            enum: argumentsObject.blockchainImplementationNames,\n        },\n        contract: {\n            type: 'string',\n            minLength: 42,\n            maxLength: 42,\n        },\n        tokenId: {\n            type: 'number',\n            minimum: 0,\n        },\n        hashFunctionId: {\n            type: 'number',\n            minimum: 1,\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v0/result-http-api-controller-v0.js",
    "content": "import { OPERATION_ID_STATUS } from '../../../constants/constants.js';\nimport BaseController from '../base-http-api-controller.js';\n\nclass ResultController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n\n        this.availableOperations = ['publish', 'get', 'query', 'local-store', 'update'];\n    }\n\n    async handleRequest(req, res) {\n        if (!this.availableOperations.includes(req.params.operation)) {\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Unsupported operation: ${req.params.operation}, available operations are: ${this.availableOperations}`,\n            });\n        }\n\n        const { operationId, operation } = req.params;\n        if (!this.operationIdService.operationIdInRightFormat(operationId)) {\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Operation id: ${operationId} is in wrong format`,\n            });\n        }\n\n        try {\n            const handlerRecord = await this.operationIdService.getOperationIdRecord(operationId);\n\n            if (handlerRecord) {\n                const response = {\n                    status: handlerRecord.status,\n                };\n                if (handlerRecord.status === OPERATION_ID_STATUS.FAILED) {\n                    response.data = JSON.parse(handlerRecord.data);\n                }\n\n                switch (operation) {\n                    case 'get':\n                    case 'publish':\n                    case 'query':\n                    case 'local-store':\n                    case 'update':\n                        if (handlerRecord.status === OPERATION_ID_STATUS.COMPLETED) {\n                            response.data = await this.operationIdService.getCachedOperationIdData(\n                                operationId,\n                            );\n                        }\n                        break;\n                    default:\n                        break;\n                }\n\n                return this.returnResponse(res, 200, response);\n            }\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Handler with id: ${operationId} does not exist.`,\n            });\n        } catch (e) {\n            this.logger.error(\n                `Error while trying to fetch ${operation} data for operation id ${operationId}. Error message: ${e.message}. ${e.stack}`,\n            );\n\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Unexpected error at getting results: ${e.message}`,\n            });\n        }\n    }\n}\n\nexport default ResultController;\n"
  },
  {
    "path": "src/controllers/http-api/v0/update-http-api-controller-v0.js",
    "content": "import BaseController from '../base-http-api-controller.js';\nimport {\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    OPERATION_STATUS,\n    LOCAL_STORE_TYPES,\n} from '../../../constants/constants.js';\n\nclass UpdateController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.updateService;\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n    }\n\n    async handleRequest(req, res) {\n        const { assertion, assertionId, blockchain, contract, tokenId } = req.body;\n\n        this.logger.info(\n            `Received asset with assertion id: ${assertionId}, blockchain: ${blockchain}, hub contract: ${contract}, token id: ${tokenId}`,\n        );\n\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.UPDATE.UPDATE_START,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_INIT_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_INIT_END,\n        );\n\n        await this.repositoryModuleManager.createOperationRecord(\n            this.operationService.getOperationName(),\n            operationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n\n        try {\n            await this.operationIdService.cacheOperationIdData(operationId, {\n                public: {\n                    assertion,\n                    assertionId,\n                },\n                blockchain,\n                contract,\n                tokenId,\n            });\n\n            const commandSequence = ['updateValidateAssetCommand', 'networkUpdateCommand'];\n\n            await this.commandExecutor.add({\n                name: commandSequence[0],\n                sequence: commandSequence.slice(1),\n                delay: 0,\n                period: 5000,\n                retries: 3,\n                data: {\n                    blockchain,\n                    contract,\n                    tokenId,\n                    assertionId,\n                    operationId,\n                    storeType: LOCAL_STORE_TYPES.TRIPLE,\n                },\n                transactional: false,\n            });\n        } catch (error) {\n            this.logger.error(\n                `Error while initializing update data: ${error.message}. ${error.stack}`,\n            );\n\n            await this.operationService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Unable to update data, Failed to process input data!',\n                ERROR_TYPE.UPDATE.UPDATE_ROUTE_ERROR,\n            );\n        }\n    }\n}\n\nexport default UpdateController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/.gitkeep",
    "content": ""
  },
  {
    "path": "src/controllers/http-api/v1/ask-http-api-controller-v1.js",
    "content": "import { OPERATION_ID_STATUS, OPERATION_STATUS, ERROR_TYPE } from '../../../constants/constants.js';\nimport BaseController from '../base-http-api-controller.js';\n\nclass AskController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.operationService = ctx.askService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.ualService = ctx.ualService;\n        this.validationService = ctx.validationService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async handleRequest(req, res) {\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.ASK.ASK_START,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            null,\n            OPERATION_ID_STATUS.ASK.ASK_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        await this.repositoryModuleManager.createOperationRecord(\n            this.operationService.getOperationName(),\n            operationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n\n        const { ual, blockchain, minimumNumberOfNodeReplications } = req.body;\n\n        try {\n            this.logger.info(`Ask for ${ual} with operation id ${operationId} initiated.`);\n\n            const commandSequence = ['askFindShardCommand', 'networkAskCommand'];\n\n            const { contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n\n            const datasetRoot =\n                await this.blockchainModuleManager.getKnowledgeCollectionLatestMerkleRoot(\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                );\n\n            await this.commandExecutor.add({\n                name: commandSequence[0],\n                sequence: commandSequence.slice(1),\n                delay: 0,\n                data: {\n                    ual,\n                    operationId,\n                    blockchain,\n                    datasetRoot,\n                    minimumNumberOfNodeReplications,\n                },\n                transactional: false,\n            });\n\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                blockchain,\n                OPERATION_ID_STATUS.ASK.ASK_END,\n            );\n        } catch (error) {\n            this.logger.error(`Error while initializing ask: ${error.message}.`);\n\n            await this.operationService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Unable to check ask, Failed to process input data!',\n                ERROR_TYPE.ASK.ASK_ERROR,\n            );\n        }\n    }\n}\n\nexport default AskController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/direct-query-http-api-controller-v1.js",
    "content": "import BaseController from '../base-http-api-controller.js';\n\nimport {\n    OPERATION_ID_STATUS,\n    TRIPLE_STORE_REPOSITORIES,\n    QUERY_TYPES,\n} from '../../../constants/constants.js';\n\nclass DirectQueryController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.config = ctx.config;\n        this.fileService = ctx.fileService;\n        this.dataService = ctx.dataService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.paranetService = ctx.paranetService;\n        this.ualService = ctx.ualService;\n        this.operationIdService = ctx.operationIdService;\n    }\n\n    async handleRequest(req, res) {\n        const { type: queryType, paranetUAL } = req.body;\n        let { query, repository } = req.body;\n\n        let data;\n        const operationId = await this.operationIdService.generateId();\n        try {\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.QUERY.QUERY_INIT_START,\n                operationId,\n            );\n            if (paranetUAL) {\n                repository = this.paranetService.getParanetRepositoryName(paranetUAL);\n            } else {\n                let tripleStoreMigrationAlreadyExecuted = false;\n                try {\n                    // TODO: If triple store is migrated we should catch this and not check every time\n                    tripleStoreMigrationAlreadyExecuted =\n                        (await this.fileService.readFile(\n                            '/root/ot-node/data/migrations/v8DataMigration',\n                        )) === 'MIGRATED';\n                } catch (e) {\n                    this.logger.warn(`No triple store migration file error: ${e}`);\n                }\n                repository =\n                    !tripleStoreMigrationAlreadyExecuted && repository\n                        ? [repository, TRIPLE_STORE_REPOSITORIES.DKG]\n                        : TRIPLE_STORE_REPOSITORIES.DKG;\n            }\n\n            const pattern = /SERVICE\\s+<([^>]+)>/g;\n            const matches = query.match(pattern);\n            if (matches?.length > 0) {\n                for (const match of matches) {\n                    const repositoryInOriginalQuery = match.split('<')[1].split('>')[0];\n                    const repositoryName = this.validateRepositoryName(repositoryInOriginalQuery);\n                    const federatedQueryRepositoryEndpoint =\n                        this.tripleStoreService.getRepositorySparqlEndpoint(repositoryName);\n                    query = query.replace(\n                        repositoryInOriginalQuery,\n                        federatedQueryRepositoryEndpoint,\n                    );\n                }\n            }\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.QUERY.QUERY_INIT_END,\n                operationId,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.QUERY.QUERY_START,\n                operationId,\n            );\n            switch (queryType) {\n                case QUERY_TYPES.CONSTRUCT: {\n                    if (Array.isArray(repository)) {\n                        const [dataV6, dataV8] = await Promise.all([\n                            this.tripleStoreService.construct(\n                                query,\n                                repository[0],\n                                this.config.modules.tripleStore.timeout.query,\n                            ),\n                            this.tripleStoreService.construct(\n                                query,\n                                repository[1],\n                                this.config.modules.tripleStore.timeout.query,\n                            ),\n                        ]);\n\n                        data = this.dataService.removeDuplicateObjectsFromArray([\n                            ...dataV6,\n                            ...dataV8,\n                        ]);\n                    } else {\n                        data = await this.tripleStoreService.construct(\n                            query,\n                            repository,\n                            this.config.modules.tripleStore.timeout.query,\n                        );\n                    }\n\n                    break;\n                }\n                case QUERY_TYPES.SELECT: {\n                    if (Array.isArray(repository)) {\n                        const [dataV6, dataV8] = await Promise.all([\n                            this.tripleStoreService.select(\n                                query,\n                                repository[0],\n                                this.config.modules.tripleStore.timeout.query,\n                            ),\n                            this.tripleStoreService.select(\n                                query,\n                                repository[1],\n                                this.config.modules.tripleStore.timeout.query,\n                            ),\n                        ]);\n\n                        data = this.dataService.removeDuplicateObjectsFromArray([\n                            ...dataV6,\n                            ...dataV8,\n                        ]);\n                    } else {\n                        data = await this.tripleStoreService.select(\n                            query,\n                            repository,\n                            this.config.modules.tripleStore.timeout.query,\n                        );\n                    }\n\n                    break;\n                }\n                default:\n                    this.returnResponse(res, 400, `Unknown query type ${queryType}`);\n                    this.operationIdService.emitChangeEvent(\n                        OPERATION_ID_STATUS.QUERY.QUERY_FAILED,\n                        operationId,\n                    );\n                    return;\n            }\n        } catch (e) {\n            this.returnResponse(res, 500, e.message);\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.QUERY.QUERY_FAILED,\n                operationId,\n            );\n            return;\n        }\n\n        this.returnResponse(res, 200, {\n            data,\n        });\n        this.operationIdService.emitChangeEvent(OPERATION_ID_STATUS.QUERY.QUERY_END, operationId);\n    }\n\n    validateRepositoryName(repository) {\n        let isParanetRepoValid = false;\n        if (this.ualService.isUAL(repository)) {\n            const paranetRepoName = this.paranetService.getParanetRepositoryName(repository);\n            isParanetRepoValid = this.config.assetSync?.syncParanets.includes(repository);\n            if (isParanetRepoValid) {\n                return paranetRepoName;\n            }\n        }\n        const isTripleStoreRepoValid =\n            Object.values(TRIPLE_STORE_REPOSITORIES).includes(repository);\n        if (isTripleStoreRepoValid) {\n            return repository;\n        }\n\n        if (!isParanetRepoValid && !isTripleStoreRepoValid) {\n            throw new Error(`Query failed! Repository with name: ${repository} doesn't exist`);\n        }\n    }\n}\n\nexport default DirectQueryController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/finality-http-api-controller-v1.js",
    "content": "import BaseController from '../base-http-api-controller.js';\n\nclass FinalityController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.operationService = ctx.finalityService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.ualService = ctx.ualService;\n        this.validationService = ctx.validationService;\n    }\n\n    async handleRequest(req, res) {\n        const { ual } = req.query;\n\n        const finality = await this.repositoryModuleManager.getFinalityAcksCount(ual || '');\n\n        if (typeof finality !== 'number')\n            return this.returnResponse(res, 400, {\n                message: 'Asset with provided UAL was not published to this node.',\n            });\n\n        this.returnResponse(res, 200, { finality });\n    }\n}\n\nexport default FinalityController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/get-http-api-controller-v1.js",
    "content": "import {\n    OPERATION_ID_STATUS,\n    OPERATION_STATUS,\n    ERROR_TYPE,\n    TRIPLES_VISIBILITY,\n    COMMAND_PRIORITY,\n} from '../../../constants/constants.js';\n\nimport BaseController from '../base-http-api-controller.js';\n\nclass GetController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.operationService = ctx.getService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.ualService = ctx.ualService;\n        this.validationService = ctx.validationService;\n        this.fileService = ctx.fileService;\n        this.paranetService = ctx.paranetService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async handleRequest(req, res) {\n        let operationId;\n        let blockchain;\n        let contract;\n        let knowledgeCollectionId;\n        let knowledgeAssetId;\n        try {\n            operationId = await this.operationIdService.generateOperationId(\n                OPERATION_ID_STATUS.GET.GET_START,\n            );\n\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                null,\n                OPERATION_ID_STATUS.GET.GET_INIT_START,\n            );\n\n            this.returnResponse(res, 202, {\n                operationId,\n            });\n\n            await this.repositoryModuleManager.createOperationRecord(\n                this.operationService.getOperationName(),\n                operationId,\n                OPERATION_STATUS.IN_PROGRESS,\n            );\n            const { paranetUAL, includeMetadata, contentType } = req.body;\n            const ual = req.body.id;\n            ({ blockchain, contract, knowledgeCollectionId, knowledgeAssetId } =\n                this.ualService.resolveUAL(ual));\n            contract = contract.toLowerCase();\n            let paranetNodesAccessPolicy;\n            this.logger.info(`Get for ${ual} with operation id ${operationId} initiated.`);\n\n            if (paranetUAL) {\n                const {\n                    contract: paranetContract,\n                    knowledgeCollectionId: paranetKnowledgeCollectionId,\n                    knowledgeAssetId: paranetKnowledgeAssetId,\n                } = this.ualService.resolveUAL(paranetUAL);\n\n                const paranetId = this.paranetService.constructParanetId(\n                    paranetContract,\n                    paranetKnowledgeCollectionId,\n                    paranetKnowledgeAssetId,\n                );\n\n                paranetNodesAccessPolicy = await this.blockchainModuleManager.getNodesAccessPolicy(\n                    blockchain,\n                    paranetId,\n                );\n            }\n\n            await this.commandExecutor.add({\n                name: 'getCommand',\n                sequence: [],\n                data: {\n                    ual,\n                    includeMetadata,\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                    knowledgeAssetId,\n                    operationId,\n                    paranetUAL,\n                    paranetNodesAccessPolicy,\n                    contentType: contentType ?? TRIPLES_VISIBILITY.ALL,\n                },\n                transactional: false,\n                priority: COMMAND_PRIORITY.HIGHEST,\n            });\n\n            await this.operationIdService.updateOperationIdStatus(\n                operationId,\n                blockchain,\n                OPERATION_ID_STATUS.GET.GET_INIT_END,\n            );\n        } catch (error) {\n            this.logger.error(`Error while initializing get data: ${error.message}.`);\n\n            await this.operationService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Unable to get data, Failed to process input data!',\n                ERROR_TYPE.GET.GET_ROUTE_ERROR,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.GET.GET_FAILED,\n                operationId,\n            );\n        }\n    }\n}\n\nexport default GetController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/info-http-api-controller-v1.js",
    "content": "import { createRequire } from 'module';\nimport BaseController from '../base-http-api-controller.js';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../../../../package.json');\n\nclass InfoController extends BaseController {\n    handleRequest(_, res) {\n        this.returnResponse(res, 200, {\n            version,\n        });\n    }\n}\n\nexport default InfoController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/local-store-http-api-controller-v1.js",
    "content": "import { kcTools } from 'assertion-tools';\nimport BaseController from '../base-http-api-controller.js';\nimport {\n    PRIVATE_HASH_SUBJECT_PREFIX,\n    TRIPLE_STORE_REPOSITORIES,\n} from '../../../constants/constants.js';\n\nclass LocalStoreController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.validationService = ctx.validationService;\n        this.ualService = ctx.ualService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.cryptoService = ctx.cryptoService;\n    }\n\n    async handleRequest(req, res) {\n        const { dataset, blockchain, datasetRoot, UAL } = req.body;\n        let contract;\n        let knowledgeCollectionId;\n        try {\n            ({ contract, knowledgeCollectionId } = this.ualService.resolveUAL(UAL));\n            const { publicKnowledgeAssetsUALs, privateKnowledgeAssetsUALs } = this.getKAUALs(\n                dataset,\n                UAL,\n            );\n\n            const alreadyInserted = await this.tripleStoreService.ask(`\n                ASK {\n                    FILTER (\n                        ${[...publicKnowledgeAssetsUALs, ...privateKnowledgeAssetsUALs]\n                            .map((ual) => `EXISTS { GRAPH <${ual}> { ?s ?p ?o } }`)\n                            .join(' && ')}\n                            )\n                            }\n                            `);\n\n            if (alreadyInserted) {\n                return this.returnResponse(res, 200, {\n                    status: true,\n                });\n            }\n        } catch (error) {\n            return this.returnResponse(res, 500, {\n                status: false,\n                error,\n            });\n        }\n\n        try {\n            const validations = [\n                this.validationService.validateDatasetRoot(dataset.public, datasetRoot),\n                this.validationService.validateDatasetRootOnBlockchain(\n                    datasetRoot,\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                ),\n            ];\n\n            if (dataset?.private?.length) {\n                validations.push(\n                    this.validationService.validatePrivateMerkleRoot(\n                        dataset.public,\n                        dataset.private,\n                    ),\n                );\n            }\n\n            await Promise.all(validations);\n        } catch (error) {\n            return this.returnResponse(res, 200, {\n                status: false,\n                error,\n            });\n        }\n        try {\n            await this.tripleStoreService.insertKnowledgeCollection(\n                TRIPLE_STORE_REPOSITORIES.DKG,\n                UAL,\n                dataset,\n            );\n\n            return this.returnResponse(res, 200, {\n                status: true,\n            });\n        } catch (error) {\n            return this.returnResponse(res, 200, {\n                status: false,\n                error,\n            });\n        }\n    }\n\n    getKAUALs(dataset, UAL) {\n        const privateHashTriples = [];\n        const filteredPublic = [];\n        let privateKnowledgeAssetsUALs = [];\n        // Check if already inserted\n        dataset.public.forEach((triple) => {\n            if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                privateHashTriples.push(triple);\n            } else {\n                filteredPublic.push(triple);\n            }\n        });\n\n        const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n            filteredPublic,\n            true,\n        );\n        publicKnowledgeAssetsTriplesGrouped.push(\n            ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n        );\n\n        let publicKnowledgeAssetsUALs = publicKnowledgeAssetsTriplesGrouped.map(\n            (_, index) => `${UAL}/${index + 1}`,\n        );\n\n        if (dataset.private?.length) {\n            const privateKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n                dataset.private,\n                true,\n            );\n\n            const publicSubjectMap = publicKnowledgeAssetsTriplesGrouped.reduce(\n                (map, group, index) => {\n                    const [publicSubject] = group[0].split(' ');\n                    map.set(publicSubject, index);\n                    return map;\n                },\n                new Map(),\n            );\n\n            for (const privateTriple of privateKnowledgeAssetsTriplesGrouped) {\n                const [privateSubject] = privateTriple[0].split(' ');\n                if (publicSubjectMap.has(privateSubject)) {\n                    const ualIndex = publicSubjectMap.get(privateSubject);\n                    privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]);\n                } else {\n                    const privateSubjectHashed = `<${PRIVATE_HASH_SUBJECT_PREFIX}${this.cryptoService.sha256(\n                        privateSubject.slice(1, -1),\n                    )}>`;\n                    if (publicSubjectMap.has(privateSubjectHashed)) {\n                        const ualIndex = publicSubjectMap.get(privateSubjectHashed);\n                        privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]);\n                    }\n                }\n            }\n        }\n        publicKnowledgeAssetsUALs = publicKnowledgeAssetsUALs.map((ual) => `${ual}/public`);\n        privateKnowledgeAssetsUALs = privateKnowledgeAssetsUALs.map((ual) => `${ual}/private`);\n        return { publicKnowledgeAssetsUALs, privateKnowledgeAssetsUALs };\n    }\n}\n\nexport default LocalStoreController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/publish-http-api-controller-v1.js",
    "content": "import BaseController from '../base-http-api-controller.js';\nimport {\n    ERROR_TYPE,\n    OPERATION_ID_STATUS,\n    OPERATION_STATUS,\n    LOCAL_STORE_TYPES,\n    COMMAND_PRIORITY,\n    PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS,\n} from '../../../constants/constants.js';\n\nclass PublishController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationService = ctx.publishService;\n        this.operationIdService = ctx.operationIdService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.pendingStorageService = ctx.pendingStorageService;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async handleRequest(req, res) {\n        const { dataset, datasetRoot, blockchain, minimumNumberOfNodeReplications } = req.body;\n\n        this.logger.info(\n            `Received asset with dataset root: ${datasetRoot}, blockchain: ${blockchain}`,\n        );\n\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_START,\n            blockchain,\n        );\n\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_INIT_START,\n            operationId,\n            blockchain,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_INIT_END,\n        );\n        await this.repositoryModuleManager.createOperationRecord(\n            this.operationService.getOperationName(),\n            operationId,\n            OPERATION_STATUS.IN_PROGRESS,\n        );\n\n        try {\n            await this.operationIdService.cacheOperationIdDataToMemory(operationId, {\n                dataset,\n                datasetRoot,\n            });\n\n            await this.operationIdService.cacheOperationIdDataToFile(operationId, {\n                dataset,\n                datasetRoot,\n            });\n\n            let effectiveMinReplications = minimumNumberOfNodeReplications;\n            let chainMinNumber = null;\n            try {\n                const chainMin = await this.blockchainModuleManager.getMinimumRequiredSignatures(\n                    blockchain,\n                );\n                chainMinNumber = Number(chainMin);\n            } catch (err) {\n                this.logger.warn(\n                    `Failed to fetch on-chain minimumRequiredSignatures for ${blockchain}: ${err.message}`,\n                );\n            }\n\n            const userMinNumber = Number(effectiveMinReplications);\n            const resolvedUserMin =\n                !Number.isNaN(userMinNumber) && userMinNumber > 0\n                    ? userMinNumber\n                    : PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS;\n\n            if (!Number.isNaN(chainMinNumber) && chainMinNumber > 0) {\n                effectiveMinReplications = Math.max(chainMinNumber, resolvedUserMin);\n            } else {\n                effectiveMinReplications = resolvedUserMin;\n            }\n\n            if (effectiveMinReplications === 0) {\n                this.logger.error(\n                    `Effective minimum replications resolved to 0 for operationId: ${operationId}, blockchain: ${blockchain}. This should never happen.`,\n                );\n            }\n\n            const publisherNodePeerId = this.networkModuleManager.getPeerId().toB58String();\n            await this.pendingStorageService.cacheDataset(\n                operationId,\n                datasetRoot,\n                dataset,\n                publisherNodePeerId,\n            );\n\n            const commandSequence = ['publishReplicationCommand'];\n\n            await this.commandExecutor.add({\n                name: commandSequence[0],\n                sequence: commandSequence.slice(1),\n                data: {\n                    datasetRoot,\n                    blockchain,\n                    operationId,\n                    storeType: LOCAL_STORE_TYPES.TRIPLE,\n                    minimumNumberOfNodeReplications: effectiveMinReplications,\n                },\n                transactional: false,\n                priority: COMMAND_PRIORITY.HIGHEST,\n            });\n        } catch (error) {\n            this.logger.error(\n                `Error while initializing publish data: ${error.message}. ${error.stack}`,\n            );\n\n            await this.operationService.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Unable to publish data, Failed to process input data!',\n                ERROR_TYPE.PUBLISH.PUBLISH_ROUTE_ERROR,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.PUBLISH.PUBLISH_FAILED,\n                operationId,\n            );\n        }\n    }\n}\n\nexport default PublishController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/query-http-api-controller-v1.js",
    "content": "import BaseController from '../base-http-api-controller.js';\n\nimport { OPERATION_ID_STATUS, TRIPLE_STORE_REPOSITORIES } from '../../../constants/constants.js';\n\nclass QueryController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.fileService = ctx.fileService;\n    }\n\n    async handleRequest(req, res) {\n        const { query, type: queryType, repository, paranetUAL } = req.body;\n\n        const operationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.QUERY.QUERY_INIT_START,\n        );\n\n        this.returnResponse(res, 202, {\n            operationId,\n        });\n\n        let tripleStoreMigrationAlreadyExecuted = false;\n        try {\n            tripleStoreMigrationAlreadyExecuted =\n                (await this.fileService.readFile(\n                    '/root/ot-node/data/migrations/v8DataMigration',\n                )) === 'MIGRATED';\n        } catch (e) {\n            this.logger.warn(`No triple store migration file error: ${e}`);\n        }\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            null,\n            OPERATION_ID_STATUS.QUERY.QUERY_INIT_END,\n        );\n\n        await this.commandExecutor.add({\n            name: 'queryCommand',\n            sequence: [],\n            delay: 0,\n            data: {\n                query,\n                queryType,\n                repository:\n                    !tripleStoreMigrationAlreadyExecuted && repository\n                        ? [repository, TRIPLE_STORE_REPOSITORIES.DKG]\n                        : TRIPLE_STORE_REPOSITORIES.DKG,\n                operationId,\n                paranetUAL,\n            },\n            transactional: false,\n        });\n    }\n}\n\nexport default QueryController;\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/.gitkeep",
    "content": ""
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/ask-schema-v1.js",
    "content": "export default (argumentsObject) => ({\n    type: 'object',\n    required: ['ual', 'blockchain', 'minimumNumberOfNodeReplications'],\n    properties: {\n        ual: {\n            oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }],\n        },\n        blockchain: {\n            enum: argumentsObject.blockchainImplementationNames,\n        },\n        minimumNumberOfNodeReplications: {\n            type: 'number',\n            minimum: 0,\n        },\n        batchSize: {\n            type: 'number',\n            minimum: 1,\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/direct-query-schema-v1.js",
    "content": "import { QUERY_TYPES } from '../../../../constants/constants.js';\n\nexport default () => ({\n    type: 'object',\n    required: ['type', 'query'],\n    properties: {\n        type: {\n            enum: [QUERY_TYPES.CONSTRUCT, QUERY_TYPES.SELECT],\n        },\n        query: {\n            type: 'string',\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/finality-schema-v1.js",
    "content": "export default () => ({\n    type: 'object',\n    required: ['ual'],\n    properties: {\n        ual: {\n            type: 'string',\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/get-schema-v1.js",
    "content": "export default () => ({\n    type: 'object',\n    required: ['id'],\n    properties: {\n        id: {\n            type: 'string',\n        },\n        contentType: {\n            type: 'string',\n        },\n        includeMetadata: {\n            type: 'boolean',\n        },\n        paranetUAL: {\n            type: ['string', 'null'],\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/local-store-schema-v1.js",
    "content": "export default (argumentsObject) => ({\n    type: 'object',\n    required: ['datasetRoot', 'dataset', 'blockchain'],\n    properties: {\n        datasetRoot: {\n            type: 'string',\n            minLength: 66,\n            maxLength: 66,\n        },\n        dataset: {\n            type: 'object',\n            properties: {\n                public: {\n                    type: 'array',\n                    items: {\n                        type: 'string',\n                    },\n                    minItems: 1,\n                },\n                private: {\n                    type: 'array',\n                    items: {\n                        type: 'string',\n                    },\n                },\n            },\n            required: ['public'],\n            additionalProperties: false,\n        },\n        blockchain: {\n            enum: argumentsObject.blockchainImplementationNames,\n        },\n        UAL: {\n            type: 'string',\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/publish-schema-v1.js",
    "content": "export default (argumentsObject) => ({\n    type: 'object',\n    required: ['datasetRoot', 'dataset', 'blockchain'],\n    properties: {\n        datasetRoot: {\n            type: 'string',\n            minLength: 66,\n            maxLength: 66,\n        },\n        dataset: {\n            type: 'object',\n            properties: {\n                public: {\n                    type: 'array',\n                    items: {\n                        type: 'string',\n                    },\n                    minItems: 1,\n                },\n                private: {\n                    type: 'array',\n                    items: {\n                        type: 'string',\n                    },\n                },\n            },\n            required: ['public'],\n            additionalProperties: false,\n        },\n        blockchain: {\n            enum: argumentsObject.blockchainImplementationNames,\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/request-schema/query-schema-v1.js",
    "content": "import { QUERY_TYPES } from '../../../../constants/constants.js';\n\nexport default () => ({\n    type: 'object',\n    required: ['type', 'query'],\n    properties: {\n        type: {\n            enum: [QUERY_TYPES.CONSTRUCT, QUERY_TYPES.SELECT],\n        },\n        query: {\n            type: 'string',\n        },\n    },\n});\n"
  },
  {
    "path": "src/controllers/http-api/v1/result-http-api-controller-v1.js",
    "content": "import {\n    NETWORK_SIGNATURES_FOLDER,\n    OPERATION_ID_STATUS,\n    PUBLISHER_NODE_SIGNATURES_FOLDER,\n} from '../../../constants/constants.js';\nimport BaseController from '../base-http-api-controller.js';\n\nclass ResultController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.operationIdService = ctx.operationIdService;\n        this.signatureService = ctx.signatureService;\n\n        this.availableOperations = ['publish', 'get', 'query', 'update', 'ask', 'finality'];\n    }\n\n    async handleRequest(req, res) {\n        if (!this.availableOperations.includes(req.params.operation)) {\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Unsupported operation: ${req.params.operation}, available operations are: ${this.availableOperations}`,\n            });\n        }\n\n        const { operationId, operation } = req.params;\n        if (!this.operationIdService.operationIdInRightFormat(operationId)) {\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Operation id: ${operationId} is in wrong format`,\n            });\n        }\n\n        try {\n            const handlerRecord = await this.operationIdService.getOperationIdRecord(operationId);\n\n            if (handlerRecord) {\n                const response = {\n                    status: handlerRecord.status,\n                };\n                if (handlerRecord.status === OPERATION_ID_STATUS.FAILED) {\n                    response.data = JSON.parse(handlerRecord.data);\n                }\n\n                switch (operation) {\n                    case 'get':\n                    case 'query':\n                    case 'finality':\n                        if (handlerRecord.status === OPERATION_ID_STATUS.COMPLETED) {\n                            response.data = await this.operationIdService.getCachedOperationIdData(\n                                operationId,\n                            );\n                        }\n                        break;\n                    case 'publish':\n                    case 'update': {\n                        const minAcksReached = handlerRecord.minAcksReached || false;\n                        response.data = { ...response.data, minAcksReached };\n                        const shouldIncludeSignatures =\n                            minAcksReached ||\n                            handlerRecord.status === OPERATION_ID_STATUS.COMPLETED;\n                        if (shouldIncludeSignatures) {\n                            try {\n                                const publisherNodeSignature = (\n                                    await this.signatureService.getSignaturesFromStorage(\n                                        PUBLISHER_NODE_SIGNATURES_FOLDER,\n                                        operationId,\n                                    )\n                                )[0];\n                                const signatures =\n                                    await this.signatureService.getSignaturesFromStorage(\n                                        NETWORK_SIGNATURES_FOLDER,\n                                        operationId,\n                                    );\n                                response.data = {\n                                    ...response.data,\n                                    minAcksReached: true,\n                                    publisherNodeSignature,\n                                    signatures,\n                                };\n                            } catch (e) {\n                                this.logger.warn(\n                                    `Failed to read signatures for operationId ${operationId}: ${e.message}`,\n                                );\n                            }\n                        }\n                        break;\n                    }\n                    case 'ask':\n                        response.data = await this.operationIdService.getCachedOperationIdData(\n                            operationId,\n                        );\n                        break;\n                    default:\n                        break;\n                }\n\n                return this.returnResponse(res, 200, response);\n            }\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Handler with id: ${operationId} does not exist.`,\n            });\n        } catch (e) {\n            this.logger.error(\n                `Error while trying to fetch ${operation} data for operation id ${operationId}. Error message: ${e.message}. ${e.stack}`,\n            );\n\n            return this.returnResponse(res, 400, {\n                code: 400,\n                message: `Unexpected error at getting results: ${e.message}`,\n            });\n        }\n    }\n}\n\nexport default ResultController;\n"
  },
  {
    "path": "src/controllers/rpc/ask-rpc-controller.js",
    "content": "import { NETWORK_MESSAGE_TYPES } from '../../constants/constants.js';\nimport BaseController from './base-rpc-controller.js';\n\nclass AskController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationService = ctx.askService;\n    }\n\n    async v1_0_0HandleRequest(message, remotePeerId, protocol) {\n        const { operationId, messageType } = message.header;\n        const [handleRequestCommand] = this.getCommandSequence(protocol);\n        let commandName;\n        switch (messageType) {\n            case NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST:\n                commandName = handleRequestCommand;\n                break;\n            default:\n                throw Error('unknown messageType');\n        }\n\n        await this.commandExecutor.add({\n            name: commandName,\n            sequence: [],\n            delay: 0,\n            data: {\n                remotePeerId,\n                operationId,\n                protocol,\n                ual: message.data.ual,\n                numberOfFoundNodes: message.data.numberOfFoundNodes,\n                blockchain: message.data.blockchain,\n            },\n            transactional: false,\n        });\n    }\n}\n\nexport default AskController;\n"
  },
  {
    "path": "src/controllers/rpc/base-rpc-controller.js",
    "content": "class BaseController {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.protocolService = ctx.protocolService;\n    }\n\n    returnResponse(res, status, data) {\n        res.status(status).send(data);\n    }\n\n    getCommandSequence(protocol) {\n        return this.protocolService.getReceiverCommandSequence(protocol);\n    }\n}\n\nexport default BaseController;\n"
  },
  {
    "path": "src/controllers/rpc/batch-get-rpc-controller.js",
    "content": "import { NETWORK_MESSAGE_TYPES } from '../../constants/constants.js';\nimport BaseController from './base-rpc-controller.js';\n\nclass BatchGetRpcController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationService = ctx.batchGetService;\n    }\n\n    async v1_0_0HandleRequest(message, remotePeerId, protocol) {\n        const { operationId, messageType } = message.header;\n        const handleRequestCommand = 'v1_0_0HandleBatchGetRequestCommand';\n        let commandName;\n        switch (messageType) {\n            case NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST:\n                commandName = handleRequestCommand;\n                break;\n            default:\n                throw Error('unknown messageType');\n        }\n\n        await this.commandExecutor.add({\n            name: commandName,\n            sequence: [],\n            delay: 0,\n            data: {\n                remotePeerId,\n                operationId,\n                protocol,\n                uals: message.data.uals,\n                blockchain: message.data.blockchain,\n                tokenIds: message.data.tokenIds,\n                includeMetadata: message.data.includeMetadata,\n            },\n            transactional: false,\n        });\n    }\n}\n\nexport default BatchGetRpcController;\n"
  },
  {
    "path": "src/controllers/rpc/finality-rpc-controller.js",
    "content": "import { NETWORK_MESSAGE_TYPES } from '../../constants/constants.js';\nimport BaseController from './base-rpc-controller.js';\n\nclass FinalityController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationService = ctx.finalityService;\n    }\n\n    async v1_0_0HandleRequest(message, remotePeerId, protocol) {\n        const { operationId, messageType } = message.header;\n        const handleRequestCommands = this.getCommandSequence(protocol);\n        let commandName;\n        switch (messageType) {\n            case NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST:\n                [commandName] = handleRequestCommands;\n                break;\n            default:\n                throw Error('unknown messageType');\n        }\n\n        await this.commandExecutor.add({\n            name: commandName,\n            sequence: [...handleRequestCommands.slice(1)],\n            delay: 0,\n            data: {\n                remotePeerId,\n                operationId,\n                protocol,\n                ual: message.data.ual,\n                blockchain: message.data.blockchain,\n                publishOperationId: message.data.publishOperationId,\n            },\n            transactional: false,\n        });\n    }\n\n    getCommandSequence(protocol) {\n        // TODO: Rework this to schedule different command for update\n        return [...this.protocolService.getReceiverCommandSequence(protocol)];\n    }\n}\n\nexport default FinalityController;\n"
  },
  {
    "path": "src/controllers/rpc/get-rpc-controller.js",
    "content": "import {\n    DEFAULT_GET_STATE,\n    NETWORK_MESSAGE_TYPES,\n    TRIPLE_STORE_REPOSITORY,\n} from '../../constants/constants.js';\nimport BaseController from './base-rpc-controller.js';\n\nclass GetController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationService = ctx.getService;\n    }\n\n    async v1_0_0HandleRequest(message, remotePeerId, protocol) {\n        const { operationId, messageType } = message.header;\n        const [handleRequestCommand] = this.getCommandSequence(protocol);\n        let commandName;\n        switch (messageType) {\n            case NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST:\n                commandName = handleRequestCommand;\n                break;\n            default:\n                throw Error('unknown messageType');\n        }\n\n        await this.commandExecutor.add({\n            name: commandName,\n            sequence: [],\n            data: {\n                remotePeerId,\n                operationId,\n                protocol,\n                ual: message.data.ual,\n                blockchain: message.data.blockchain,\n                contract: message.data.contract,\n                knowledgeCollectionId: message.data.knowledgeCollectionId,\n                tokenIds: message.data.tokenIds,\n                knowledgeAssetId: message.data.knowledgeAssetId,\n                includeMetadata: message.data.includeMetadata,\n                state: message.data.state ?? DEFAULT_GET_STATE,\n                paranetUAL: message.data.paranetUAL,\n                paranetId: message.data.paranetId,\n                migrationFlag: message.data.migrationFlag,\n                repository: message.data.repository ?? TRIPLE_STORE_REPOSITORY.DKG,\n            },\n            transactional: false,\n        });\n    }\n}\n\nexport default GetController;\n"
  },
  {
    "path": "src/controllers/rpc/publish-rpc-controller.js",
    "content": "import BaseController from './base-rpc-controller.js';\nimport { NETWORK_MESSAGE_TYPES, COMMAND_PRIORITY } from '../../constants/constants.js';\n\nclass PublishController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.publishService;\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n    }\n\n    async v1_0_0HandleRequest(message, remotePeerId, protocol) {\n        const { operationId, messageType } = message.header;\n\n        const command = { sequence: [], transactional: false, data: {} };\n        const [handleRequestCommand] = this.getCommandSequence(protocol);\n        if (messageType === NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST) {\n            Object.assign(command, {\n                name: handleRequestCommand,\n                priority: COMMAND_PRIORITY.HIGHEST,\n            });\n\n            await this.operationIdService.cacheOperationIdDataToMemory(operationId, {\n                dataset: message.data.dataset,\n                datasetRoot: message.data.datasetRoot,\n            });\n\n            await this.operationIdService.cacheOperationIdDataToFile(operationId, {\n                dataset: message.data.dataset,\n                datasetRoot: message.data.datasetRoot,\n            });\n        } else {\n            throw new Error('Unknown message type');\n        }\n\n        command.data = {\n            ...command.data,\n            remotePeerId,\n            operationId,\n            protocol,\n            dataset: message.data.dataset,\n            datasetRoot: message.data.datasetRoot,\n            blockchain: message.data.blockchain,\n            isOperationV0: message.data.isOperationV0,\n            contract: message.data.contract,\n            tokenId: message.data.tokenId,\n        };\n\n        await this.commandExecutor.add(command);\n    }\n}\n\nexport default PublishController;\n"
  },
  {
    "path": "src/controllers/rpc/rpc-router.js",
    "content": "class RpcRouter {\n    constructor(ctx) {\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n\n        this.protocolService = ctx.protocolService;\n        this.logger = ctx.logger;\n\n        this.publishRpcController = ctx.publishRpcController;\n        this.getRpcController = ctx.getRpcController;\n        this.updateRpcController = ctx.updateRpcController;\n        this.askRpcController = ctx.askRpcController;\n        this.finalityRpcController = ctx.finalityRpcController;\n        this.batchGetRpcController = ctx.batchGetRpcController;\n    }\n\n    initialize() {\n        this.initializeListeners();\n    }\n\n    initializeListeners() {\n        const protocols = this.protocolService.getProtocols().flatMap((p) => p);\n\n        for (const protocol of protocols) {\n            const version = this.protocolService.toAwilixVersion(protocol);\n            const operation = this.protocolService.toOperation(protocol);\n            const handleRequest = `${version}HandleRequest`;\n            const controller = `${operation}RpcController`;\n            const blockchainImplementations = this.blockchainModuleManager.getImplementationNames();\n\n            this.networkModuleManager.handleMessage(protocol, (message, remotePeerId) => {\n                const modifiedMessage = this.modifyMessage(message, blockchainImplementations);\n                this[controller][handleRequest](modifiedMessage, remotePeerId, protocol);\n            });\n        }\n    }\n\n    modifyMessage(message, blockchainImplementations) {\n        const modifiedMessage = message;\n        if (modifiedMessage.data.blockchain?.split(':').length === 1) {\n            for (const implementation of blockchainImplementations) {\n                if (implementation.split(':')[0] === modifiedMessage.data.blockchain) {\n                    modifiedMessage.data.blockchain = implementation;\n                    break;\n                }\n            }\n        }\n        return modifiedMessage;\n    }\n}\n\nexport default RpcRouter;\n"
  },
  {
    "path": "src/controllers/rpc/update-rpc-controller.js",
    "content": "import BaseController from './base-rpc-controller.js';\nimport { NETWORK_MESSAGE_TYPES } from '../../constants/constants.js';\n\nclass UpdateController extends BaseController {\n    constructor(ctx) {\n        super(ctx);\n        this.operationService = ctx.updateService;\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n    }\n\n    async v1_0_0HandleRequest(message, remotePeerId, protocol) {\n        const { operationId, messageType } = message.header;\n\n        const command = { sequence: [], delay: 0, transactional: false, data: {} };\n        let dataSource;\n        const [handleInitCommand, handleRequestCommand] = this.getCommandSequence(protocol);\n        switch (messageType) {\n            case NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_INIT:\n                dataSource = message.data;\n                command.name = handleInitCommand;\n                command.period = 5000;\n                command.retries = 3;\n                break;\n            case NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_REQUEST:\n                // eslint-disable-next-line no-case-declarations\n                dataSource = await this.operationIdService.getCachedOperationIdData(operationId);\n                await this.operationIdService.cacheOperationIdData(operationId, {\n                    assertionId: dataSource.assertionId,\n                    assertion: message.data.assertion,\n                });\n                command.name = handleRequestCommand;\n                break;\n            default:\n                throw Error('unknown message type');\n        }\n\n        command.data = {\n            ...command.data,\n            remotePeerId,\n            operationId,\n            protocol,\n            assertionId: dataSource.assertionId,\n            blockchain: dataSource.blockchain,\n            contract: dataSource.contract,\n            tokenId: dataSource.tokenId,\n        };\n\n        await this.commandExecutor.add(command);\n    }\n}\n\nexport default UpdateController;\n"
  },
  {
    "path": "src/logger/logger.js",
    "content": "import { pino } from 'pino';\nimport pretty from 'pino-pretty';\n\n/**\n * Class for logging messages.\n */\nclass Logger {\n    /**\n     * Create a new logger.\n     * @param {string} logLevel - The log level to use for the logger.\n     */\n    constructor(logLevel = 'trace', pinoInstance = null) {\n        this.logLevel = logLevel;\n        this._timers = new Map();\n        if (!pinoInstance) this.initialize(logLevel);\n        else this.pinoLogger = pinoInstance;\n    }\n\n    /**\n     * Initialize the logger.\n     * @param {string} logLevel - The log level to use for the logger.\n     */\n    initialize(logLevel) {\n        try {\n            const stream = pretty({\n                colorize: true,\n                level: this.logLevel,\n                translateTime: 'yyyy-mm-dd HH:MM:ss',\n                ignore: 'pid,hostname,Event_name,Operation_name,Id_operation',\n                hideObject: true,\n                messageFormat: (log, messageKey) => {\n                    const { commandId, commandName, operationId } = log;\n                    let context = '';\n                    if (operationId) context += `{Operation ID: ${operationId}} `;\n                    if (commandName) context += `[${commandName}] `;\n                    if (commandId) context += `(Command ID: ${commandId}) `;\n                    return `${context} ${log[messageKey]}`;\n                },\n            });\n            this.pinoLogger = pino(\n                {\n                    customLevels: {\n                        emit: 15,\n                        api: 25,\n                    },\n                    level: logLevel,\n                },\n                stream,\n            );\n        } catch (e) {\n            // eslint-disable-next-line no-console\n            console.error(`Failed to create logger. Error message: ${e.message}`);\n        }\n    }\n\n    /**\n     * Create a child logger with the given bindings.\n     * @param {pino.Bindings} bindings - The bindings to use for the child logger.\n     * @return {Logger} The child logger.\n     */\n    child(bindings) {\n        return new Logger(this.logLevel, this.pinoLogger.child(bindings, {}));\n    }\n\n    /**\n     * Restart the logger.\n     */\n    restart() {\n        this.initialize(this.logLevel, true);\n    }\n\n    // ===========================\n    // =====   TIMERS    ========\n    // ===========================\n\n    /**\n     * Start a timer countdown. Equivalent to console.time(label).\n     * @param {string} label - The label to use for the timer.\n     */\n    startTimer(label) {\n        // TODO: Maybe add dedicated level just for timers?\n        // if (this.pinoLogger.levelVal > this.pinoLogger.levels.values.trace)\n        //     return;\n\n        this._timers.set(label, process.hrtime.bigint());\n    }\n\n    /**\n     * End a timer countdown. Should be used only in trace level, equivalent to console.timeEnd(label).\n     * @note Requires startTimer to be called first.\n     * @param {string} label - The label to use for the timer.\n     */\n    endTimer(label) {\n        const start = this._timers.get(label);\n        if (!start) return;\n\n        this._timers.delete(label);\n        const diffNs = process.hrtime.bigint() - start;\n        // TODO: Fix precision on micro/nano seconds scale\n        // eslint-disable-next-line no-undef\n        const diffMs = Number(diffNs / BigInt(1e6)).toFixed(2);\n\n        this.pinoLogger.trace(`${label} - ${diffMs}ms`);\n    }\n\n    // ===========================\n    // ====   LOG LEVELS    ======\n    // ===========================\n\n    /**\n     * Log a silent message.\n     * @param {any} obj - The object to log.\n     */\n    silent(obj) {\n        this.pinoLogger.silent(obj);\n    }\n\n    /**\n     * Log a fatal message.\n     * @param {any} obj - The object to log.\n     */\n    fatal(obj) {\n        this.pinoLogger.fatal(obj);\n    }\n\n    /**\n     * Log an error message.\n     * @param {any} obj - The object to log.\n     */\n    error(obj) {\n        this.pinoLogger.error(obj);\n    }\n\n    /**\n     * Log a warning message.\n     * @param {any} obj - The object to log.\n     */\n    warn(obj) {\n        this.pinoLogger.warn(obj);\n    }\n\n    /**\n     * Log an info message.\n     * @param {any} obj - The object to log.\n     */\n    info(obj) {\n        this.pinoLogger.info(obj);\n    }\n\n    /**\n     * Log a debug message.\n     * @param {any} obj - The object to log.\n     */\n    debug(obj) {\n        this.pinoLogger.debug(obj);\n    }\n\n    /**\n     * Log an emit message.\n     * @param {any} obj - The object to log.\n     */\n    emit(obj) {\n        // TODO: Check if confused with node.js event emitter\n        this.pinoLogger.emit(obj);\n    }\n\n    /**\n     * Log a trace message.\n     * @param {any} obj - The object to log.\n     */\n    trace(obj) {\n        this.pinoLogger.trace(obj);\n    }\n\n    /**\n     * Log an API message.\n     * @param {any} obj - The object to log.\n     */\n    api(obj) {\n        this.pinoLogger.api(obj);\n    }\n\n    /**\n     * Close the logger.\n     * @param {string} closingMessage - The message to log when closing the logger.\n     */\n    closeLogger(closingMessage) {\n        const finalLogger = pino.final(this.pinoLogger);\n        finalLogger.info(closingMessage);\n    }\n}\n\nexport default Logger;\n"
  },
  {
    "path": "src/migration/base-migration.js",
    "content": "import path from 'path';\nimport FileService from '../service/file-service.js';\n\nclass BaseMigration {\n    constructor(migrationName, logger, config) {\n        if (!migrationName || migrationName === '') {\n            throw new Error('Unable to initialize base migration: name not passed in constructor.');\n        }\n        if (!logger) {\n            throw new Error(\n                'Unable to initialize base migration: logger object not passed in constructor.',\n            );\n        }\n        if (!config) {\n            throw new Error(\n                'Unable to initialize base migration: config object not passed in constructor.',\n            );\n        }\n        this.migrationName = migrationName;\n        this.logger = logger;\n        this.config = config;\n        this.fileService = new FileService({ config: this.config, logger: this.logger });\n    }\n\n    async migrationAlreadyExecuted(migrationName = null) {\n        const migrationFilePath = path.join(\n            this.fileService.getMigrationFolderPath(),\n            migrationName ?? this.migrationName,\n        );\n        if (await this.fileService.pathExists(migrationFilePath)) {\n            return true;\n        }\n        return false;\n    }\n\n    async migrate(migrationPath = null) {\n        this.logger.info(`Starting ${this.migrationName} migration.`);\n        this.startedTimestamp = Date.now();\n\n        await this.executeMigration();\n\n        const migrationFolderPath = migrationPath || this.fileService.getMigrationFolderPath();\n        await this.fileService.writeContentsToFile(\n            migrationFolderPath,\n            this.migrationName,\n            'MIGRATED',\n        );\n        this.logger.info(\n            `${this.migrationName} migration completed. Lasted: ${\n                Date.now() - this.startedTimestamp\n            } millisecond(s).`,\n        );\n    }\n\n    async getMigrationInfo(migrationName = null) {\n        const migrationFolderPath = this.fileService.getMigrationFolderPath();\n        const migrationInfoFileName = `${migrationName ?? this.migrationName}_info`;\n        const migrationInfoPath = path.join(migrationFolderPath, migrationInfoFileName);\n        let migrationInfo = null;\n        if (await this.fileService.pathExists(migrationInfoPath)) {\n            migrationInfo = await this.fileService\n                .readFile(migrationInfoPath, true)\n                .catch(() => {});\n        }\n        return migrationInfo;\n    }\n\n    async saveMigrationInfo(migrationInfo) {\n        const migrationFolderPath = this.fileService.getMigrationFolderPath();\n        const migrationInfoFileName = `${this.migrationName}_info`;\n        await this.fileService.writeContentsToFile(\n            migrationFolderPath,\n            migrationInfoFileName,\n            JSON.stringify(migrationInfo),\n            false,\n        );\n    }\n\n    async executeMigration() {\n        throw Error('Execute migration method not implemented');\n    }\n}\n\nexport default BaseMigration;\n"
  },
  {
    "path": "src/migration/migration-executor.js",
    "content": "import path from 'path';\n\nimport { NODE_ENVIRONMENTS } from '../constants/constants.js';\nimport TripleStoreUserConfigurationMigration from './triple-store-user-configuration-migration.js';\nimport RedisSetupMigration from './redis-setup-migration.js';\n\nclass MigrationExecutor {\n    static async executeTripleStoreUserConfigurationMigration(container, logger, config) {\n        if (\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.TEST ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVNET\n        )\n            return;\n\n        const migration = new TripleStoreUserConfigurationMigration(\n            'tripleStoreUserConfigurationMigrationV8',\n            logger,\n            config,\n        );\n        if (!(await migration.migrationAlreadyExecuted())) {\n            try {\n                await migration.migrate();\n            } catch (error) {\n                logger.error(\n                    `Unable to execute triple store user configuration  migration. Error: ${error.message}`,\n                );\n            }\n        }\n    }\n\n    static async executeRedisSetupMigration(container, logger, config) {\n        if (\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.TEST ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVNET\n        )\n            return;\n        const migration = new RedisSetupMigration('redisSetupMigration', logger, config);\n\n        if (!(await migration.migrationAlreadyExecuted())) {\n            try {\n                await migration.migrate();\n            } catch (error) {\n                logger.error(`Unable to execute redis setup migration. Error: ${error.message}`);\n            }\n        }\n    }\n\n    static exitNode(code = 0) {\n        process.exit(code);\n    }\n\n    static async migrationAlreadyExecuted(migrationName, fileService) {\n        const migrationFilePath = path.join(fileService.getMigrationFolderPath(), migrationName);\n        if (await fileService.pathExists(migrationFilePath)) {\n            return true;\n        }\n        return false;\n    }\n}\n\nexport default MigrationExecutor;\n"
  },
  {
    "path": "src/migration/redis-setup-migration.js",
    "content": "import { execSync } from 'child_process';\nimport BaseMigration from './base-migration.js';\nimport { NODE_ENVIRONMENTS } from '../constants/constants.js';\n\nclass RedisSetupMigration extends BaseMigration {\n    async executeMigration() {\n        if (\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.TEST\n        ) {\n            this.logger.info('Skipping Redis setup in development/test environment');\n            return;\n        }\n\n        if (this.isRedisInstalledAndRunning()) {\n            this.logger.info('✅ Redis is already installed and running.');\n\n            // Check if configuration is correct\n            if (this.isRedisConfiguredCorrectly()) {\n                this.logger.info('✅ Redis is configured correctly. No changes needed.');\n                return;\n            }\n\n            this.logger.info('⚠️ Redis is installed but configuration needs updating.');\n            this.updateRedisConfiguration();\n        } else {\n            this.logger.info('🔧 Installing Redis...');\n            this.installRedis();\n        }\n\n        this.verifyRedisInstallation();\n    }\n\n    installRedis() {\n        this.run('sudo apt update', 'Updating package list');\n        this.run('sudo apt install -y redis-server', 'Installing Redis server');\n\n        // Backup original config before modifying\n        this.run(\n            'sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.backup',\n            'Backing up original redis.conf',\n        );\n\n        this.updateRedisConfiguration();\n\n        this.run('sudo systemctl restart redis.service', 'Restarting Redis service');\n        this.run('sudo systemctl enable redis.service', 'Enabling Redis to start on boot');\n        this.run('sudo systemctl status redis.service --no-pager', 'Checking Redis service status');\n    }\n\n    updateRedisConfiguration() {\n        this.logger.info('🔧 Updating Redis configuration...');\n\n        // Ensure redis.conf uses systemd\n        this.modifyRedisConf('supervised', 'supervised systemd', 'Enabling systemd supervision');\n\n        // Enable AOF persistence\n        this.modifyRedisConf('appendonly', 'appendonly yes', 'Enabling AOF persistence');\n        this.modifyRedisConf(\n            'appendfsync',\n            'appendfsync everysec',\n            'Setting AOF fsync every second',\n        );\n\n        // Enforce noeviction policy\n        this.modifyRedisConf(\n            'maxmemory-policy',\n            'maxmemory-policy noeviction',\n            'Setting noeviction policy',\n        );\n\n        // Restart Redis to apply configuration changes\n        this.run(\n            'sudo systemctl restart redis.service',\n            'Restarting Redis service to apply configuration',\n        );\n    }\n\n    isRedisConfiguredCorrectly() {\n        try {\n            const configPath = '/etc/redis/redis.conf';\n            const configContent = execSync(`sudo cat ${configPath}`, { stdio: 'pipe' }).toString();\n\n            const checks = [\n                { pattern: /supervised\\s+systemd/, name: 'systemd supervision' },\n                { pattern: /appendonly\\s+yes/, name: 'AOF persistence' },\n                { pattern: /appendfsync\\s+everysec/, name: 'AOF fsync every second' },\n                { pattern: /maxmemory-policy\\s+noeviction/, name: 'noeviction policy' },\n            ];\n\n            let allCorrect = true;\n            for (const check of checks) {\n                if (!check.pattern.test(configContent)) {\n                    this.logger.warn(`⚠️ Configuration issue: ${check.name} not set correctly`);\n                    allCorrect = false;\n                }\n            }\n\n            if (allCorrect) {\n                this.logger.info('✅ All Redis configuration checks passed');\n            } else {\n                this.logger.info('⚠️ Some Redis configuration settings need to be updated');\n            }\n\n            return allCorrect;\n        } catch (err) {\n            this.logger.warn(`⚠️ Could not read Redis configuration: ${err.message}`);\n            return false;\n        }\n    }\n\n    isRedisInstalledAndRunning() {\n        try {\n            execSync('which redis-server', { stdio: 'ignore' });\n            const serviceStatus = execSync('systemctl is-active redis.service', { stdio: 'pipe' })\n                .toString()\n                .trim();\n            const ping = execSync('redis-cli ping', { stdio: 'pipe' }).toString().trim();\n            return serviceStatus === 'active' && ping === 'PONG';\n        } catch {\n            return false;\n        }\n    }\n\n    verifyRedisInstallation() {\n        try {\n            const ping = execSync('redis-cli ping').toString().trim();\n            if (ping === 'PONG') {\n                this.logger.info('🎉 Redis is installed and responding: PONG');\n\n                // Final configuration check\n                if (this.isRedisConfiguredCorrectly()) {\n                    this.logger.info(\n                        '🎉 Redis setup completed successfully with correct configuration!',\n                    );\n                } else {\n                    this.logger.error('❌ Redis is running but configuration is still incorrect');\n                    process.exit(1);\n                }\n            } else {\n                this.logger.error('❌ Redis did not respond with PONG');\n                process.exit(1);\n            }\n        } catch (err) {\n            this.logger.error('❌ Redis ping failed');\n            this.logger.error(err.message);\n            process.exit(1);\n        }\n    }\n\n    run(cmd, description) {\n        this.logger.info(`🔧 ${description}...`);\n        try {\n            const output = execSync(cmd, { stdio: 'inherit' });\n            return output?.toString().trim();\n        } catch (err) {\n            this.logger.error(`❌ Failed: ${description}`);\n            this.logger.error(err.message);\n            process.exit(1);\n        }\n    }\n\n    modifyRedisConf(pattern, replacement, description) {\n        const configPath = '/etc/redis/redis.conf';\n\n        // Use a simpler approach: comment out lines that contain the pattern (not commented)\n        const commentCommand = `sudo sed -i '/^[[:space:]]*${pattern}/s/^/# /' ${configPath}`;\n        this.run(commentCommand, `Commenting out existing ${description} settings`);\n\n        // Step 2: Add the new setting at the end of the file\n        this.run(\n            `echo '${replacement}' | sudo tee -a ${configPath}`,\n            `Adding ${replacement} to redis.conf`,\n        );\n\n        // Step 3: Verify the change was made\n        try {\n            const grepCheck = `grep -q \"^${replacement}$\" ${configPath}`;\n            execSync(grepCheck, { stdio: 'ignore' });\n            this.logger.info(`✅ Successfully updated: ${description}`);\n        } catch {\n            this.logger.error(`❌ Failed to verify: ${description}`);\n            throw new Error(`Failed to update ${description}`);\n        }\n    }\n}\n\nexport default RedisSetupMigration;\n"
  },
  {
    "path": "src/migration/triple-store-user-configuration-migration.js",
    "content": "import appRootPath from 'app-root-path';\nimport path from 'path';\nimport BaseMigration from './base-migration.js';\n\nclass TripleStoreUserConfigurationMigration extends BaseMigration {\n    async executeMigration() {\n        const configurationFolderPath = path.join(appRootPath.path, '..');\n        const configurationFilePath = path.join(\n            configurationFolderPath,\n            this.config.configFilename,\n        );\n\n        const userConfiguration = await this.fileService.readFile(configurationFilePath, true);\n\n        if ('tripleStore' in userConfiguration.modules) {\n            const oldConfigTripleStore = userConfiguration.modules;\n            for (const implementation in oldConfigTripleStore.tripleStore.implementation) {\n                if (oldConfigTripleStore.tripleStore.implementation[implementation].enabled) {\n                    const { url, username, password } =\n                        oldConfigTripleStore.tripleStore.implementation[implementation].config\n                            .repositories.publicCurrent;\n\n                    oldConfigTripleStore.tripleStore.implementation[\n                        implementation\n                    ].config.repositories.dkg = {\n                        url,\n                        name: 'dkg',\n                        username,\n                        password,\n                    };\n                }\n            }\n\n            await this.fileService.writeContentsToFile(\n                configurationFolderPath,\n                this.config.configFilename,\n                JSON.stringify(userConfiguration, null, 4),\n            );\n        }\n    }\n}\n\nexport default TripleStoreUserConfigurationMigration;\n"
  },
  {
    "path": "src/modules/auto-updater/auto-updater-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass AutoUpdaterModuleManager extends BaseModuleManager {\n    getName() {\n        return 'autoUpdater';\n    }\n\n    /**\n     * @typedef VersionResults\n     * @param {Boolean} UpToDate - If the local version is the same as the remote version.\n     * @param {String} currentVersion - The version of the local application.\n     * @param {String} remoteVersion - The version of the application in the git repository.\n     *\n     * Checks the local version of the application against the remote repository.\n     * @returns Promise{VersionResults} - An object with the results of the version comparison.\n     */\n    async compareVersions() {\n        if (this.initialized) {\n            return this.getImplementation().module.compareVersions();\n        }\n    }\n\n    /**\n     * Clones the git repository and installs the update over the local application.\n     * A backup of the application is created before the update is installed.\n     * If configured, a completion command will be executed and the process for the app will be stopped.\n     */\n    async update() {\n        if (this.initialized) {\n            return this.getImplementation().module.update();\n        }\n    }\n}\n\nexport default AutoUpdaterModuleManager;\n"
  },
  {
    "path": "src/modules/auto-updater/implementation/ot-auto-updater.js",
    "content": "import path from 'path';\nimport fs from 'fs-extra';\nimport { exec } from 'child_process';\nimport https from 'https';\nimport appRootPath from 'app-root-path';\nimport semver from 'semver';\nimport axios from 'axios';\nimport unzipper from 'unzipper';\n\nconst REPOSITORY_URL = 'https://github.com/OriginTrail/dkg-engine';\nconst ARCHIVE_REPOSITORY_URL = 'github.com/OriginTrail/dkg-engine/archive/';\n\nclass OTAutoUpdater {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n        if (!this.config) throw Error('You must pass a config object to AutoUpdater.');\n        if (!this.config.branch) this.config.branch = 'master';\n    }\n\n    async compareVersions() {\n        try {\n            this.logger.debug('AutoUpdater - Comparing versions...');\n            const currentVersion = await this.readAppVersion(appRootPath.path);\n            const remoteVersion = await this.readRemoteVersion();\n            this.logger.debug(`AutoUpdater - Current version: ${currentVersion}`);\n            this.logger.debug(`AutoUpdater - Remote Version: ${remoteVersion}`);\n            if (currentVersion === remoteVersion) {\n                return {\n                    upToDate: true,\n                    currentVersion,\n                };\n            }\n            return {\n                upToDate: false,\n                currentVersion,\n                remoteVersion,\n            };\n        } catch (e) {\n            this.logger.error(\n                `AutoUpdater - Error comparing local and remote versions. Error message: ${e.message}`,\n            );\n            return {\n                upToDate: false,\n                currentVersion: 'Error',\n                remoteVersion: 'Error',\n            };\n        }\n    }\n\n    async update() {\n        try {\n            this.logger.debug(`AutoUpdater - Updating dkg-engine from ${REPOSITORY_URL}`);\n            const currentDirectory = appRootPath.path;\n            const rootPath = path.join(currentDirectory, '..');\n\n            const currentVersion = await this.readAppVersion(currentDirectory);\n            const newVersion = await this.readRemoteVersion();\n            const updateDirectory = path.join(rootPath, newVersion);\n            const zipArchiveDestination = `${updateDirectory}.zip`;\n            const tmpExtractionPath = path.join(rootPath, 'TmpExtractionPath');\n            await this.downloadUpdate(zipArchiveDestination);\n            await this.unzipFile(tmpExtractionPath, zipArchiveDestination);\n            await this.moveAndCleanExtractedData(tmpExtractionPath, updateDirectory);\n            await this.copyConfigFiles(currentDirectory, updateDirectory);\n            await this.installDependencies(updateDirectory);\n\n            const currentSymlinkFolder = path.join(rootPath, 'current');\n            if (await fs.pathExists(currentSymlinkFolder)) {\n                await fs.remove(currentSymlinkFolder);\n            }\n            await fs.ensureSymlink(updateDirectory, currentSymlinkFolder);\n\n            this.logger.debug('AutoUpdater - Finished installing updated version.');\n\n            await this.removeOldVersions(currentVersion, newVersion);\n            return true;\n        } catch (e) {\n            this.logger.error(`AutoUpdater - Error updating application. Error message: ${e}`);\n            return false;\n        }\n    }\n\n    async removeOldVersions(currentVersion, newVersion) {\n        try {\n            const rootPath = path.join(appRootPath.path, '..');\n\n            const oldVersionsDirs = (await fs.promises.readdir(rootPath, { withFileTypes: true }))\n                .filter((dirent) => dirent.isDirectory())\n                .map((dirent) => dirent.name)\n                .filter(\n                    (name) => semver.valid(name) && name !== newVersion && name !== currentVersion,\n                );\n            const deletePromises = oldVersionsDirs\n                .map((dirName) => path.join(rootPath, dirName))\n                .map((fullPath) => fs.promises.rm(fullPath, { recursive: true, force: true }));\n\n            await Promise.all(deletePromises);\n        } catch (e) {\n            throw Error('AutoUpdater - There was an error removing old versions');\n        }\n    }\n\n    /**\n     * Copies user config files to destination directory\n     */\n    async copyConfigFiles(source, destination) {\n        this.logger.debug('AutoUpdater - Copying config files...');\n        this.logger.debug(`AutoUpdater - Destination: ${destination}`);\n\n        await fs.ensureDir(destination);\n\n        const envFilePath = path.join(source, '.env');\n        const newEnvFilePath = path.join(destination, '.env');\n        await fs.copy(envFilePath, newEnvFilePath);\n    }\n\n    /**\n     * Reads the applications version from the package.json file.\n     */\n    async readAppVersion(appPath) {\n        const file = path.join(appPath, 'package.json');\n        this.logger.debug(`AutoUpdater - Reading app version from ${file}`);\n        const appPackage = await fs.promises.readFile(file);\n        return JSON.parse(appPackage).version;\n    }\n\n    /**\n     * A promise wrapper for sending a get https requests.\n     * @param {String} url - The Https address to request.\n     * @param {String} options - The request options.\n     */\n    promiseHttpsRequest(url, options) {\n        return new Promise((resolve, reject) => {\n            const req = https.request(url, options, (res) => {\n                let body = '';\n                res.on('data', (data) => {\n                    body += data;\n                });\n                res.on('end', () => {\n                    if (res.statusCode === 200) return resolve(body);\n                    this.logger.warn(`AutoUpdater - Bad Response ${res.statusCode}`);\n                    reject(res.statusCode);\n                });\n            });\n            this.logger.debug(`AutoUpdater - Sending request to ${url}`);\n            req.on('error', reject);\n            req.end();\n        });\n    }\n\n    /**\n     * Reads the applications version from the git repository.\n     */\n    async readRemoteVersion() {\n        const options = {};\n        let url = `${REPOSITORY_URL}/${this.config.branch}/package.json`;\n        if (url.includes('github')) url = url.replace('github.com', 'raw.githubusercontent.com');\n        this.logger.debug(`AutoUpdater - Reading remote version from ${url}`);\n\n        try {\n            const body = await this.promiseHttpsRequest(url, options);\n            const remotePackage = JSON.parse(body);\n            const { version } = remotePackage;\n            return version;\n        } catch (e) {\n            throw Error(\n                `This repository requires a token or does not exist. Error message: ${e.message}`,\n            );\n        }\n    }\n\n    downloadUpdate(destination) {\n        return new Promise((resolve, reject) => {\n            const url = `https://${path.join(ARCHIVE_REPOSITORY_URL, this.config.branch)}.zip`;\n            this.logger.debug(`AutoUpdater - Downloading dkg-engine .zip file from url: ${url}`);\n            axios({ method: 'get', url, responseType: 'stream' })\n                .then((response) => {\n                    const fileStream = fs.createWriteStream(destination);\n                    response.data.pipe(fileStream);\n                    fileStream.on('finish', () => {\n                        fileStream.close(); // close() is async, call cb after close completes.\n                        resolve();\n                    });\n                    fileStream.on('error', (err) => {\n                        // Handle errors\n                        fs.unlinkSync(destination);\n                        reject(err);\n                    });\n                })\n                .catch((error) => {\n                    reject(\n                        Error(\n                            `AutoUpdater - Unable to download new version of dkg-engine. Error: ${error.message}`,\n                        ),\n                    );\n                });\n        });\n    }\n\n    unzipFile(destination, source) {\n        this.logger.debug(`AutoUpdater - Unzipping dkg-engine new version archive`);\n        return new Promise((resolve, reject) => {\n            const fileReadStream = fs\n                .createReadStream(source)\n                .pipe(unzipper.Extract({ path: destination }));\n            fileReadStream.on('close', () => {\n                this.logger.debug(`AutoUpdater - Unzip completed`);\n                fs.removeSync(source);\n                resolve();\n            });\n            fileReadStream.on('error', (err) => {\n                reject(err);\n            });\n        });\n    }\n\n    async moveAndCleanExtractedData(extractedDataPath, destinationPath) {\n        this.logger.debug(`AutoUpdater - Cleaning update destination directory`);\n        const destinationDirFiles = await fs.readdir(extractedDataPath);\n        if (destinationDirFiles.length !== 1) {\n            await fs.remove(extractedDataPath);\n            throw Error('Extracted archive for new dkg-engine version is not valid');\n        }\n        const sourcePath = path.join(extractedDataPath, destinationDirFiles[0]);\n        await fs.remove(destinationPath);\n        await fs.move(sourcePath, destinationPath);\n\n        await fs.remove(extractedDataPath);\n    }\n\n    /**\n     * Runs npm install to update/install the application dependencies.\n     */\n    installDependencies(destination) {\n        return new Promise((resolve, reject) => {\n            this.logger.debug(\n                `AutoUpdater - Installing application dependencies in ${destination}`,\n            );\n\n            const command = `cd ${destination} && npm ci --omit=dev --ignore-scripts`;\n            const child = exec(command);\n            let rejected = false;\n            child.stdout.on('data', (data) => {\n                this.logger.trace(`AutoUpdater - npm ci - ${data.replace(/\\r?\\n|\\r/g, '')}`);\n            });\n\n            child.stderr.on('data', (data) => {\n                if (data.includes('ERROR')) {\n                    this.logger.trace(`Error message: ${data}`);\n                    // npm passes warnings as errors, only reject if \"error\" is included\n                    const errorData = data.replace(/\\r?\\n|\\r/g, '');\n                    this.logger.error(\n                        `AutoUpdater - Error installing dependencies. Error message: ${errorData}`,\n                    );\n                    if (!rejected) {\n                        rejected = true;\n                        reject(errorData);\n                    }\n                }\n            });\n            child.stdout.on('end', () => {\n                if (!rejected) {\n                    this.logger.debug(`AutoUpdater - Dependencies installed successfully`);\n                    resolve();\n                }\n            });\n        });\n    }\n}\n\nexport default OTAutoUpdater;\n"
  },
  {
    "path": "src/modules/base-module-manager.js",
    "content": "import ModuleConfigValidation from './module-config-validation.js';\nimport { REQUIRED_MODULES } from '../constants/constants.js';\n\nclass BaseModuleManager {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n\n        this.moduleConfigValidation = new ModuleConfigValidation(ctx);\n    }\n\n    async initialize() {\n        try {\n            const moduleConfig = this.config.modules[this.getName()];\n            this.moduleConfigValidation.validateModule(this.getName(), moduleConfig);\n\n            this.handlers = {};\n            for (const implementationName in moduleConfig.implementation) {\n                if (!moduleConfig.implementation[implementationName].enabled) {\n                    // eslint-disable-next-line no-continue\n                    continue;\n                }\n                const implementationConfig = moduleConfig.implementation[implementationName];\n\n                if (!implementationConfig) {\n                    this.logger.warn(\n                        `${implementationName} module implementation configuration not defined.`,\n                    );\n                    return false;\n                }\n\n                if (!implementationConfig.package) {\n                    this.logger.warn(`Package for ${this.getName()} module is not defined`);\n                    return false;\n                }\n\n                implementationConfig.config.appDataPath = this.config.appDataPath;\n                // eslint-disable-next-line no-await-in-loop\n                const ModuleClass = (await import(implementationConfig.package)).default;\n                const module = new ModuleClass();\n                // eslint-disable-next-line no-await-in-loop\n                await module.initialize(implementationConfig.config, this.logger);\n                module.getImplementationName = () => implementationName;\n\n                this.logger.info(\n                    `${this.getName()} module initialized with implementation: ${implementationName}`,\n                );\n                this.handlers[implementationName] = {\n                    module,\n                    config: implementationConfig.config,\n                };\n            }\n            this.initialized = true;\n\n            return true;\n        } catch (error) {\n            if (REQUIRED_MODULES.includes(this.getName())) {\n                throw new Error(\n                    `${this.getName()} module is required but got error during initialization - ${\n                        error.message\n                    }`,\n                );\n            }\n            this.logger.error(error.message);\n\n            return false;\n        }\n    }\n\n    getName() {\n        throw new Error('Get name method not implemented in child class of base module interface.');\n    }\n\n    getImplementation(name = null) {\n        const keys = Object.keys(this.handlers);\n        if (keys.length === 1 || !name) {\n            return this.handlers[keys[0]];\n        }\n        return this.handlers[name];\n    }\n\n    getImplementationNames() {\n        return Object.keys(this.handlers);\n    }\n\n    removeImplementation(name = null) {\n        const keys = Object.keys(this.handlers);\n        if (keys.length === 1 || !name) {\n            delete this.handlers[keys[0]];\n        }\n        delete this.handlers[name];\n    }\n\n    getModuleConfiguration(name = null) {\n        return this.getImplementation(name).config;\n    }\n}\n\nexport default BaseModuleManager;\n"
  },
  {
    "path": "src/modules/blockchain/blockchain-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass BlockchainModuleManager extends BaseModuleManager {\n    getName() {\n        return 'blockchain';\n    }\n\n    callImplementationFunction(blockchain, functionName, args = []) {\n        if (this.getImplementation(blockchain)) {\n            return this.getImplementation(blockchain).module[functionName](...args);\n        }\n    }\n\n    initializeTransactionQueues(blockchain, concurrency) {\n        return this.callImplementationFunction(blockchain, 'getTotalTransactionQueueLength', [\n            concurrency,\n        ]);\n    }\n\n    getTotalTransactionQueueLength(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getTotalTransactionQueueLength');\n    }\n\n    async initializeContracts(blockchain) {\n        return this.callImplementationFunction(blockchain, 'initializeContracts');\n    }\n\n    initializeAssetStorageContract(blockchain, contractAddress) {\n        return this.callImplementationFunction(blockchain, 'initializeAssetStorageContract', [\n            contractAddress,\n        ]);\n    }\n\n    initializeContract(blockchain, contractName, contractAddress) {\n        return this.callImplementationFunction(blockchain, 'initializeContract', [\n            contractName,\n            contractAddress,\n        ]);\n    }\n\n    getContractAddress(blockchain, contractName) {\n        return this.callImplementationFunction(blockchain, 'getContractAddress', [contractName]);\n    }\n\n    setContractCallCache(blockchain, contractName, functionName, value) {\n        return this.callImplementationFunction(blockchain, 'setContractCallCache', [\n            contractName,\n            functionName,\n            value,\n        ]);\n    }\n\n    getPublicKeys(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getPublicKeys');\n    }\n\n    getManagementKey(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getManagementKey');\n    }\n\n    async isAssetStorageContract(blockchain, contractAddress) {\n        return this.callImplementationFunction(blockchain, 'isAssetStorageContract', [\n            contractAddress,\n        ]);\n    }\n\n    async getBlockNumber(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getBlockNumber');\n    }\n\n    async getIdentityId(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getIdentityId');\n    }\n\n    async identityIdExists(blockchain) {\n        return this.callImplementationFunction(blockchain, 'identityIdExists');\n    }\n\n    async createProfile(blockchain, peerId) {\n        return this.callImplementationFunction(blockchain, 'createProfile', [peerId]);\n    }\n\n    async getGasPrice(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getGasPrice');\n    }\n\n    async healthCheck(blockchain) {\n        return this.callImplementationFunction(blockchain, 'healthCheck');\n    }\n\n    async restartService(blockchain) {\n        return this.callImplementationFunction(blockchain, 'restartService');\n    }\n\n    async getKnowledgeCollectionMerkleRootByIndex(\n        blockchain,\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n        index,\n    ) {\n        return this.callImplementationFunction(blockchain, 'getCollectionMerkleRootByIndex', [\n            assetStorageContractAddress,\n            knowledgeCollectionId,\n            index,\n        ]);\n    }\n\n    async getKnowledgeCollectionLatestMerkleRoot(\n        blockchain,\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n    ) {\n        return this.callImplementationFunction(\n            blockchain,\n            'getKnowledgeCollectionLatestMerkleRoot',\n            [assetStorageContractAddress, knowledgeCollectionId],\n        );\n    }\n\n    async getLatestKnowledgeCollectionId(blockchain, assetStorageContractAddress) {\n        return this.callImplementationFunction(blockchain, 'getLatestKnowledgeCollectionId', [\n            assetStorageContractAddress,\n        ]);\n    }\n\n    getAssetStorageContractAddresses(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getAssetStorageContractAddresses');\n    }\n\n    async getKnowledgeCollectionMerkleRoots(\n        blockchain,\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n    ) {\n        return this.callImplementationFunction(blockchain, 'getKnowledgeCollectionMerkleRoots', [\n            assetStorageContractAddress,\n            knowledgeCollectionId,\n        ]);\n    }\n\n    // async getKnowledgeAssetOwner(blockchain, assetContractAddress, tokenId) {\n    //     return this.callImplementationFunction(blockchain, 'getKnowledgeAssetOwner', [\n    //         assetContractAddress,\n    //         tokenId,\n    //     ]);\n    // }\n\n    async getLatestMerkleRootPublisher(\n        blockchain,\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n    ) {\n        return this.callImplementationFunction(blockchain, 'getLatestMerkleRootPublisher', [\n            assetStorageContractAddress,\n            knowledgeCollectionId,\n        ]);\n    }\n\n    async getShardingTableHead(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getShardingTableHead');\n    }\n\n    async getShardingTableLength(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getShardingTableLength');\n    }\n\n    async getShardingTablePage(blockchain, startingIdentityId, nodesNum) {\n        return this.callImplementationFunction(blockchain, 'getShardingTablePage', [\n            startingIdentityId,\n            nodesNum,\n        ]);\n    }\n\n    async getKnowledgeCollectionSize(blockchain, knowledgeCollectionId) {\n        return this.callImplementationFunction(blockchain, 'getKnowledgeCollectionSize', [\n            knowledgeCollectionId,\n        ]);\n    }\n\n    async getKnowledgeAssetsRange(blockchain, assetStorageContractAddress, knowledgeCollectionId) {\n        return this.callImplementationFunction(blockchain, 'getKnowledgeAssetsRange', [\n            assetStorageContractAddress,\n            knowledgeCollectionId,\n        ]);\n    }\n\n    async getParanetKnowledgeCollectionCount(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'getParanetKnowledgeCollectionCount', [\n            paranetId,\n        ]);\n    }\n\n    async getParanetKnowledgeCollectionLocatorsWithPagination(\n        blockchain,\n        paranetId,\n        offset,\n        limit,\n    ) {\n        return this.callImplementationFunction(\n            blockchain,\n            'getParanetKnowledgeCollectionLocatorsWithPagination',\n            [paranetId, offset, limit],\n        );\n    }\n\n    async getMinimumStake(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getMinimumStake');\n    }\n\n    async getMaximumStake(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getMaximumStake');\n    }\n\n    async getMinimumRequiredSignatures(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getMinimumRequiredSignatures');\n    }\n\n    async getLatestBlock(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getLatestBlock');\n    }\n\n    async getBlockchainTimestamp(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getBlockchainTimestamp');\n    }\n\n    async getParanetMetadata(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'getParanetMetadata', [paranetId]);\n    }\n\n    async getParanetName(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'getParanetName', [paranetId]);\n    }\n\n    async getDescription(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'getDescription', [paranetId]);\n    }\n\n    async paranetExists(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'paranetExists', [paranetId]);\n    }\n\n    async isPermissionedNode(blockchain, paranetId, identityId) {\n        return this.callImplementationFunction(blockchain, 'isPermissionedNode', [\n            paranetId,\n            identityId,\n        ]);\n    }\n\n    async getNodesAccessPolicy(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'getNodesAccessPolicy', [paranetId]);\n    }\n\n    async getPermissionedNodes(blockchain, paranetId) {\n        return this.callImplementationFunction(blockchain, 'getPermissionedNodes', [paranetId]);\n    }\n\n    async getNodeId(blockchain, identityId) {\n        return this.callImplementationFunction(blockchain, 'getNodeId', [identityId]);\n    }\n\n    async signMessage(blockchain, messageHash) {\n        return this.callImplementationFunction(blockchain, 'signMessage', [messageHash]);\n    }\n\n    async getStakeWeightedAverageAsk(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getStakeWeightedAverageAsk', []);\n    }\n\n    async getTimeUntilNextEpoch(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getTimeUntilNextEpoch', []);\n    }\n\n    async getEpochLength(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getEpochLength', []);\n    }\n\n    async isKnowledgeCollectionRegistered(blockchain, paranetId, knowledgeCollectionId) {\n        return this.callImplementationFunction(blockchain, 'isKnowledgeCollectionRegistered', [\n            paranetId,\n            knowledgeCollectionId,\n        ]);\n    }\n\n    async getActiveProofPeriodStatus(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getActiveProofPeriodStatus');\n    }\n\n    async createChallenge(blockchain) {\n        return this.callImplementationFunction(blockchain, 'createChallenge', []);\n    }\n\n    async getNodeChallenge(blockchain, nodeId) {\n        return this.callImplementationFunction(blockchain, 'getNodeChallenge', [nodeId]);\n    }\n\n    async submitProof(blockchain, chunk, merkleProof) {\n        return this.callImplementationFunction(blockchain, 'submitProof', [chunk, merkleProof]);\n    }\n\n    async getNodeEpochProofPeriodScore(blockchain, nodeId, epoch, proofPeriodStartBlock) {\n        return this.callImplementationFunction(blockchain, 'getNodeEpochProofPeriodScore', [\n            nodeId,\n            epoch,\n            proofPeriodStartBlock,\n        ]);\n    }\n\n    async getTransaction(blockchain, txHash) {\n        return this.callImplementationFunction(blockchain, 'getTransaction', [txHash]);\n    }\n\n    async getBlockTimestamp(blockchain, blockNumber) {\n        return this.callImplementationFunction(blockchain, 'getBlockTimestamp', [blockNumber]);\n    }\n\n    async getDelegators(blockchain, identityId) {\n        return this.callImplementationFunction(blockchain, 'getDelegators', [identityId]);\n    }\n\n    async getLastClaimedEpoch(blockchain, identityId, address) {\n        return this.callImplementationFunction(blockchain, 'getLastClaimedEpoch', [\n            identityId,\n            address,\n        ]);\n    }\n\n    async hasEverDelegated(blockchain, identityId, address) {\n        return this.callImplementationFunction(blockchain, 'hasEverDelegated', [\n            identityId,\n            address,\n        ]);\n    }\n\n    async getCurrentEpoch(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getCurrentEpoch', []);\n    }\n\n    async batchClaimDelegatorRewards(blockchain, identityId, epochs, delegators) {\n        return this.callImplementationFunction(blockchain, 'batchClaimDelegatorRewards', [\n            identityId,\n            epochs,\n            delegators,\n        ]);\n    }\n\n    async getAssetStorageContractsAddress(blockchain) {\n        return this.callImplementationFunction(blockchain, 'getAssetStorageContractsAddress');\n    }\n\n    // SUPPORT FOR OLD CONTRACTS\n    async getLatestAssertionId(blockchain, assetContractAddress, tokenId) {\n        return this.callImplementationFunction(blockchain, 'getLatestAssertionId', [\n            assetContractAddress,\n            tokenId,\n        ]);\n    }\n\n    getImplementation(name = null) {\n        const keys = Object.keys(this.handlers);\n        if (!keys.includes(name)) {\n            throw new Error(`Blockchain: ${name} implementation is not enabled.`);\n        }\n        return this.handlers[name];\n    }\n}\n\nexport default BlockchainModuleManager;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/base/base-service.js",
    "content": "import Web3Service from '../web3-service.js';\n\nclass BaseService extends Web3Service {\n    constructor(ctx) {\n        super(ctx);\n\n        this.baseTokenTicker = 'ETH';\n        this.tracTicker = 'TRAC';\n    }\n\n    async getGasPrice() {\n        return this.provider.getGasPrice();\n    }\n}\n\nexport default BaseService;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/eth/eth-service.js",
    "content": "import Web3Service from '../web3-service.js';\n\nclass EthService extends Web3Service {\n    constructor(ctx) {\n        super(ctx);\n\n        this.baseTokenTicker = 'ETH';\n        this.tracTicker = 'TRAC';\n    }\n}\n\nexport default EthService;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/gnosis/gnosis-service.js",
    "content": "import axios from 'axios';\nimport ethers from 'ethers';\nimport Web3Service from '../web3-service.js';\nimport { GNOSIS_DEFAULT_GAS_PRICE, NODE_ENVIRONMENTS } from '../../../../constants/constants.js';\n\nclass GnosisService extends Web3Service {\n    constructor(ctx) {\n        super(ctx);\n\n        this.baseTokenTicker = 'GNO';\n        this.tracTicker = 'TRAC';\n\n        this.defaultGasPrice = ethers.utils.parseUnits(\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.MAINNET\n                ? GNOSIS_DEFAULT_GAS_PRICE.MAINNET.toString()\n                : GNOSIS_DEFAULT_GAS_PRICE.TESTNET.toString(),\n            'gwei',\n        );\n    }\n\n    async getGasPrice() {\n        let gasPrice;\n\n        try {\n            const response = await axios.get(this.config.gasPriceOracleLink);\n            if (response?.data?.average) {\n                // returns gwei\n                gasPrice = Number(response.data.average);\n                this.logger.debug(`Gas price from Gnosis oracle link: ${gasPrice} gwei`);\n                gasPrice = ethers.utils.parseUnits(gasPrice.toString(), 'gwei');\n            } else if (response?.data?.result) {\n                // returns wei\n                gasPrice = Number(response.data.result, 10);\n                this.logger.debug(`Gas price from Gnosis oracle link: ${gasPrice} wei`);\n            } else {\n                this.logger.warn(\n                    `Gas price oracle: ${this.config.gasPriceOracleLink} returns gas price in unsupported format. Using default value: ${this.defaultGasPrice} Gwei.`,\n                );\n                return this.defaultGasPrice;\n            }\n        } catch (error) {\n            this.logger.warn(\n                `Failed to fetch the gas price from the Gnosis: ${error}. Using default value: ${this.defaultGasPrice} Gwei.`,\n            );\n            return this.defaultGasPrice;\n        }\n        if (gasPrice && gasPrice.gt && gasPrice.gt(this.defaultGasPrice)) {\n            return gasPrice;\n        }\n\n        return this.defaultGasPrice;\n    }\n\n    buildTransactionGasParams(gasPrice) {\n        const minPriorityFee = ethers.BigNumber.from(2_000_000_000);\n\n        let maxPriorityFeePerGas = minPriorityFee;\n        if (ethers.BigNumber.isBigNumber(gasPrice)) {\n            const derived = gasPrice.div(5);\n            if (derived.gt(minPriorityFee)) {\n                maxPriorityFeePerGas = derived;\n            }\n        }\n\n        const maxFeePerGas =\n            ethers.BigNumber.isBigNumber(gasPrice) && gasPrice.gt(maxPriorityFeePerGas)\n                ? gasPrice\n                : maxPriorityFeePerGas.mul(2);\n\n        return { maxFeePerGas, maxPriorityFeePerGas };\n    }\n\n    async healthCheck() {\n        try {\n            const blockNumber = await this.getBlockNumber();\n            if (blockNumber) return true;\n        } catch (e) {\n            this.logger.error(`Error on checking Gnosis blockchain. ${e}`);\n            return false;\n        }\n        return false;\n    }\n}\n\nexport default GnosisService;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/hardhat/hardhat-service.js",
    "content": "import ethers from 'ethers';\nimport Web3Service from '../web3-service.js';\n\nclass HardhatService extends Web3Service {\n    constructor(ctx) {\n        super(ctx);\n        this.baseTokenTicker = 'HARDHAT_TOKENS';\n        this.tracTicker = 'gTRAC';\n    }\n\n    async getBlockchainTimestamp() {\n        const latestBlock = await super.getLatestBlock();\n        return latestBlock.timestamp;\n    }\n\n    async providerReady() {\n        return this.provider.ready;\n    }\n\n    async getGasPrice() {\n        return ethers.utils.parseUnits('20', 'wei');\n    }\n}\n\nexport default HardhatService;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/ot-parachain/ot-parachain-service.js",
    "content": "import { ApiPromise, WsProvider, HttpProvider } from '@polkadot/api';\nimport { ethers } from 'ethers';\nimport { NEURO_DEFAULT_GAS_PRICE, NODE_ENVIRONMENTS } from '../../../../constants/constants.js';\nimport Web3Service from '../web3-service.js';\n\nconst NATIVE_TOKEN_DECIMALS = 12;\n\nclass OtParachainService extends Web3Service {\n    constructor(ctx) {\n        super(ctx);\n\n        this.baseTokenTicker = 'OTP';\n        this.tracTicker = 'TRAC';\n    }\n\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n        this.rpcNumber = 0;\n        await this.initializeParachainProvider();\n        // await this.checkEvmWallets();\n        await this.parachainProvider.disconnect();\n        await super.initialize(config, logger);\n    }\n\n    async checkEvmWallets() {\n        this.invalidWallets = [];\n        for (const wallet of this.config.operationalWallets) {\n            // eslint-disable-next-line no-await-in-loop\n            const walletMapped = await this.checkEvmAccountMapping(wallet.evmAddress);\n            if (!walletMapped) {\n                this.invalidWallets.push(wallet);\n            }\n        }\n        if (this.invalidWallets.length === this.config.operationalWallets.length) {\n            throw Error('Unable to find mappings for all operational wallets');\n        }\n        this.invalidWallets.forEach((wallet) =>\n            this.logger.warn(\n                `Unable to find account mapping for wallet: ${wallet.evmAddress}, wallet removed from the list`,\n            ),\n        );\n        const { evmManagementWalletPublicKey } = this.config;\n        const managementWalletMapped = await this.checkEvmAccountMapping(\n            evmManagementWalletPublicKey,\n        );\n        if (!managementWalletMapped) {\n            throw Error('Missing account mapping for management wallet');\n        }\n    }\n\n    async checkEvmAccountMapping(walletPublicKey) {\n        const account = await this.queryParachainState('evmAccounts', 'accounts', [\n            walletPublicKey,\n        ]);\n        if (!account || account.toHex() === '0x') {\n            return false;\n        }\n        return true;\n    }\n\n    async callParachainExtrinsic(keyring, extrinsic, method, args) {\n        let result;\n        while (!result) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                result = this.parachainProvider.tx[extrinsic][method](...args).signAndSend(keyring);\n\n                return result;\n            } catch (error) {\n                // eslint-disable-next-line no-await-in-loop\n                await this.handleParachainError(error, method);\n            }\n        }\n    }\n\n    async queryParachainState(state, method, args) {\n        let result;\n        while (!result) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                result = await this.parachainProvider.query[state][method](...args);\n\n                return result;\n            } catch (error) {\n                // eslint-disable-next-line no-await-in-loop\n                await this.handleParachainError(error, method);\n            }\n        }\n    }\n\n    async initializeParachainProvider() {\n        let tries = 0;\n        let isRpcConnected = false;\n        while (!isRpcConnected) {\n            if (tries >= this.config.rpcEndpoints.length) {\n                throw Error(\n                    'Blockchain initialisation failed, unable to initialize parachain provider!',\n                );\n            }\n\n            try {\n                let provider;\n                if (this.config.rpcEndpoints[this.rpcNumber].startsWith('ws')) {\n                    provider = new WsProvider(this.config.rpcEndpoints[this.rpcNumber]);\n                } else {\n                    provider = new HttpProvider(this.config.rpcEndpoints[this.rpcNumber]);\n                }\n                // eslint-disable-next-line no-await-in-loop\n                this.parachainProvider = await new ApiPromise({ provider }).isReadyOrError;\n                isRpcConnected = true;\n            } catch (e) {\n                this.logger.warn(\n                    `Unable to create parachain provider for endpoint : ${\n                        this.config.rpcEndpoints[this.rpcNumber]\n                    }. Error: ${e.message}`,\n                );\n                tries += 1;\n                this.rpcNumber = (this.rpcNumber + 1) % this.config.rpcEndpoints.length;\n            }\n        }\n    }\n\n    async getGasPrice() {\n        if (this.config.gasPriceOracleLink) return super.getGasPrice();\n\n        try {\n            return this.provider.getGasPrice();\n        } catch (error) {\n            const defaultGasPrice =\n                process.env.NODE_ENV === NODE_ENVIRONMENTS.MAINNET\n                    ? NEURO_DEFAULT_GAS_PRICE.MAINNET\n                    : NEURO_DEFAULT_GAS_PRICE.TESTNET;\n\n            return ethers.utils.parseUnits(defaultGasPrice.toString(), 'wei');\n        }\n    }\n\n    async handleParachainError(error, method) {\n        let isRpcError = false;\n        try {\n            await this.parachainProvider.rpc.net.listening();\n        } catch (rpcError) {\n            isRpcError = true;\n            this.logger.warn(\n                `Unable to execute substrate method ${method} using blockchain rpc : ${\n                    this.config.rpcEndpoints[this.rpcNumber]\n                }.`,\n            );\n            await this.restartParachainProvider();\n        }\n        if (!isRpcError) throw error;\n    }\n\n    async getLatestTokenId(assetContractAddress) {\n        return this.provider.getStorageAt(assetContractAddress.toString().toLowerCase(), 7);\n    }\n\n    async restartParachainProvider() {\n        this.rpcNumber = (this.rpcNumber + 1) % this.config.rpcEndpoints.length;\n        this.logger.warn(\n            `There was an issue with current parachain provider. Connecting to ${\n                this.config.rpcEndpoints[this.rpcNumber]\n            }`,\n        );\n        await this.initializeParachainProvider();\n    }\n\n    async getNativeTokenBalance(wallet) {\n        const nativeBalance = await wallet.getBalance();\n        return nativeBalance / 10 ** NATIVE_TOKEN_DECIMALS;\n    }\n\n    getValidOperationalWallets() {\n        const wallets = [];\n        this.config.operationalWallets.forEach((wallet) => {\n            if (\n                this.invalidWallets?.find(\n                    (invalidWallet) => invalidWallet.privateKey === wallet.privateKey,\n                )\n            ) {\n                this.logger.warn(\n                    `Skipping initialization of wallet. Wallet public key: ${wallet.evmAddress}`,\n                );\n            } else {\n                try {\n                    wallets.push(new ethers.Wallet(wallet.privateKey, this.provider));\n                } catch (error) {\n                    this.logger.warn(\n                        `Invalid evm private key, unable to create wallet instance. Wallet public key: ${wallet.evmAddress}. Error: ${error.message}`,\n                    );\n                }\n            }\n        });\n        return wallets;\n    }\n}\n\nexport default OtParachainService;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/polygon/polygon-service.js",
    "content": "import Web3Service from '../web3-service.js';\n\nclass PolygonService extends Web3Service {\n    constructor(ctx) {\n        super(ctx);\n\n        this.baseTokenTicker = 'MATIC';\n        this.tracTicker = 'mTRAC';\n    }\n}\n\nexport default PolygonService;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/web3-service-validator.js",
    "content": "class Web3ServiceValidator {\n    static validateResult(functionName, contractName, result, logger) {\n        if (Web3ServiceValidator[`${functionName}Validator`]) {\n            logger.trace(\n                `Calling web3 service validator for function name: ${functionName}, contract: ${contractName}`,\n            );\n            return Web3ServiceValidator[`${functionName}Validator`](result);\n        }\n        return true;\n    }\n}\n\nexport default Web3ServiceValidator;\n"
  },
  {
    "path": "src/modules/blockchain/implementation/web3-service.js",
    "content": "/* eslint-disable no-await-in-loop */\nimport { ethers, BigNumber } from 'ethers';\nimport axios from 'axios';\nimport async from 'async';\nimport { setTimeout as sleep } from 'timers/promises';\n\nimport {\n    SOLIDITY_ERROR_STRING_PREFIX,\n    SOLIDITY_PANIC_CODE_PREFIX,\n    SOLIDITY_PANIC_REASONS,\n    ZERO_PREFIX,\n    TRANSACTION_QUEUE_CONCURRENCY,\n    TRANSACTION_POLLING_TIMEOUT_MILLIS,\n    TRANSACTION_CONFIRMATIONS,\n    WS_RPC_PROVIDER_PRIORITY,\n    HTTP_RPC_PROVIDER_PRIORITY,\n    FALLBACK_PROVIDER_QUORUM,\n    RPC_PROVIDER_STALL_TIMEOUT,\n    CACHED_FUNCTIONS,\n    CACHE_DATA_TYPES,\n    CONTRACTS,\n    CONTRACT_FUNCTION_PRIORITY,\n    TRANSACTION_PRIORITY,\n    CONTRACT_FUNCTION_GAS_LIMIT_INCREASE_FACTORS,\n    ABIs,\n    EXPECTED_TRANSACTION_ERRORS,\n} from '../../../constants/constants.js';\nimport Web3ServiceValidator from './web3-service-validator.js';\n\nclass Web3Service {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n        this.contractCallCache = {};\n        await this.initializeWeb3();\n        this.initializeTransactionQueues();\n        await this.initializeContracts();\n\n        this.initializeProviderDebugging();\n    }\n\n    initializeTransactionQueues(concurrency = TRANSACTION_QUEUE_CONCURRENCY) {\n        this.transactionQueues = {};\n        for (const operationalWallet of this.operationalWallets) {\n            const transactionQueue = async.priorityQueue((args, cb) => {\n                const { contractInstance, functionName, transactionArgs, gasPrice } = args;\n                this._executeContractFunction(\n                    contractInstance,\n                    functionName,\n                    transactionArgs,\n                    gasPrice,\n                    operationalWallet,\n                )\n                    .then((result) => {\n                        cb({ result });\n                    })\n                    .catch((error) => {\n                        cb({ error });\n                    });\n            }, concurrency);\n            this.transactionQueues[operationalWallet.address] = transactionQueue;\n        }\n        this.transactionQueueOrder = Object.keys(this.transactionQueues);\n    }\n\n    queueTransaction(contractInstance, functionName, transactionArgs, callback, gasPrice) {\n        const selectedQueue = this.selectTransactionQueue();\n        const priority = CONTRACT_FUNCTION_PRIORITY[functionName] ?? TRANSACTION_PRIORITY.MEDIUM;\n        this.logger.info(`Calling ${functionName} with priority: ${priority}`);\n        selectedQueue.push(\n            {\n                contractInstance,\n                functionName,\n                transactionArgs,\n                gasPrice,\n            },\n            priority,\n            callback,\n        );\n    }\n\n    removeTransactionQueue(walletAddress) {\n        delete this.transactionQueues[walletAddress];\n    }\n\n    getTotalTransactionQueueLength() {\n        let totalLength = 0;\n        Object.values(this.transactionQueues).forEach((queue) => {\n            totalLength += queue.length();\n        });\n        return totalLength;\n    }\n\n    selectTransactionQueue() {\n        const queues = Object.keys(this.transactionQueues).map((wallet) => ({\n            wallet,\n            length: this.transactionQueues[wallet].length(),\n        }));\n        const minLength = Math.min(...queues.map((queue) => queue.length));\n        const shortestQueues = queues.filter((queue) => queue.length === minLength);\n        if (shortestQueues.length === 1) {\n            return this.transactionQueues[shortestQueues[0].wallet];\n        }\n\n        const selectedQueueWallet = this.transactionQueueOrder.find((roundRobinNext) =>\n            shortestQueues.some((shortestQueue) => shortestQueue.wallet === roundRobinNext),\n        );\n\n        this.transactionQueueOrder.push(\n            this.transactionQueueOrder\n                .splice(this.transactionQueueOrder.indexOf(selectedQueueWallet), 1)\n                .pop(),\n        );\n        return this.transactionQueues[selectedQueueWallet];\n    }\n\n    getValidOperationalWallets() {\n        const wallets = [];\n        this.config.operationalWallets.forEach((wallet) => {\n            try {\n                wallets.push(new ethers.Wallet(wallet.privateKey, this.provider));\n            } catch (error) {\n                this.logger.warn(\n                    `Invalid evm private key, unable to create wallet instance. Wallet public key: ${wallet.evmAddress}. Error: ${error.message}`,\n                );\n            }\n        });\n        return wallets;\n    }\n\n    getRandomOperationalWallet() {\n        const randomIndex = Math.floor(Math.random() * this.operationalWallets.length);\n        return this.operationalWallets[randomIndex];\n    }\n\n    async initializeWeb3() {\n        const providers = [];\n        for (const rpcEndpoint of this.config.rpcEndpoints) {\n            const isWebSocket = rpcEndpoint.startsWith('ws');\n            const Provider = isWebSocket\n                ? ethers.providers.WebSocketProvider\n                : ethers.providers.JsonRpcProvider;\n            const priority = isWebSocket ? WS_RPC_PROVIDER_PRIORITY : HTTP_RPC_PROVIDER_PRIORITY;\n\n            try {\n                const provider = new Provider(rpcEndpoint);\n                // eslint-disable-next-line no-await-in-loop\n                await provider.getNetwork();\n\n                providers.push({\n                    provider,\n                    priority,\n                    weight: 1,\n                    stallTimeout: RPC_PROVIDER_STALL_TIMEOUT,\n                });\n\n                this.logger.debug(\n                    `Connected to the blockchain RPC: ${this.maskRpcUrl(rpcEndpoint)}.`,\n                );\n            } catch (e) {\n                this.logger.warn(\n                    `Unable to connect to the blockchain RPC: ${this.maskRpcUrl(rpcEndpoint)}.`,\n                );\n            }\n        }\n\n        try {\n            this.provider = new ethers.providers.FallbackProvider(\n                providers,\n                FALLBACK_PROVIDER_QUORUM,\n            );\n\n            // eslint-disable-next-line no-await-in-loop\n            await this.providerReady();\n        } catch (e) {\n            throw new Error(\n                `RPC Fallback Provider initialization failed. Fallback Provider quorum: ${FALLBACK_PROVIDER_QUORUM}. Error: ${e.message}.`,\n            );\n        }\n\n        this.operationalWallets = this.getValidOperationalWallets();\n        if (this.operationalWallets.length === 0) {\n            throw Error(\n                'Unable to initialize web3 service, all operational wallets provided are invalid',\n            );\n        }\n    }\n\n    async initializeContracts() {\n        this.contracts = {};\n        this.contractAddresses = {};\n\n        this.logger.info(\n            `Initializing contracts with hub contract address: ${this.config.hubContractAddress}`,\n        );\n        this.contracts.Hub = new ethers.Contract(\n            this.config.hubContractAddress,\n            ABIs.Hub,\n            this.operationalWallets[0],\n        );\n        this.contractAddresses[this.config.hubContractAddress] = this.contracts.Hub;\n\n        const contractsArray = await this.callContractFunction(\n            this.contracts.Hub,\n            'getAllContracts',\n            [],\n        );\n\n        contractsArray.forEach(([contractName, contractAddress]) => {\n            this.initializeContract(contractName, contractAddress);\n        });\n\n        this.assetStorageContracts = {};\n        const assetStoragesArray = await this.callContractFunction(\n            this.contracts.Hub,\n            'getAllAssetStorages',\n            [],\n        );\n        assetStoragesArray.forEach(([, assetStorageAddress]) => {\n            this.initializeAssetStorageContract(assetStorageAddress);\n        });\n\n        this.logger.info(`Contracts initialized`);\n\n        await this.logBalances();\n    }\n\n    initializeProviderDebugging() {\n        this.provider.on('debug', (info) => {\n            const { method } = info.request;\n\n            if (['call', 'estimateGas'].includes(method)) {\n                const contractInstance = this.contractAddresses[info.request.params.transaction.to];\n                const inputData = info.request.params.transaction.data;\n                const decodedInputData = this._decodeInputData(\n                    inputData,\n                    contractInstance.interface,\n                );\n                const functionFragment = contractInstance.interface.getFunction(\n                    inputData.slice(0, 10),\n                );\n                const functionName = functionFragment.name;\n                const inputs = functionFragment.inputs\n                    .map((input, i) => {\n                        const argName = input.name;\n                        const argValue = this._formatArgument(decodedInputData[i]);\n                        return `${argName}=${argValue}`;\n                    })\n                    .join(', ');\n                if (info.backend.error) {\n                    const decodedErrorData = this._decodeErrorData(\n                        info.backend.error,\n                        contractInstance.interface,\n                    );\n                    this.logger.debug(\n                        `${functionName}(${inputs})  ${method} has failed; Error: ${decodedErrorData}; ` +\n                            `RPC: ${this.maskRpcUrl(info.backend.provider.connection.url)}.`,\n                    );\n                } else if (info.backend.result !== undefined) {\n                    let message = `${functionName}(${inputs}) ${method} has been successfully executed; `;\n\n                    if (info.backend.result !== null && method !== 'estimateGas') {\n                        try {\n                            const decodedResultData = this._decodeResultData(\n                                inputData.slice(0, 10),\n                                info.backend.result,\n                                contractInstance.interface,\n                            );\n                            message += `Result: ${decodedResultData}; `;\n                        } catch (error) {\n                            this.logger.warn(\n                                `Unable to decode result data for. Message: ${message}`,\n                            );\n                        }\n                    }\n\n                    message += `RPC: ${this.maskRpcUrl(info.backend.provider.connection.url)}.`;\n\n                    this.logger.debug(message);\n                }\n            }\n        });\n    }\n\n    maskRpcUrl(url) {\n        if (url.includes('apiKey')) {\n            return url.split('apiKey')[0];\n        }\n        return url;\n    }\n\n    initializeAssetStorageContract(assetStorageAddress) {\n        this.assetStorageContracts[assetStorageAddress.toLowerCase()] = new ethers.Contract(\n            assetStorageAddress,\n            ABIs.KnowledgeCollectionStorage,\n            this.operationalWallets[0],\n        );\n        this.contractAddresses[assetStorageAddress] =\n            this.assetStorageContracts[assetStorageAddress.toLowerCase()];\n    }\n\n    setContractCallCache(contractName, functionName, value) {\n        if (CACHED_FUNCTIONS[contractName]?.[functionName]) {\n            const type = CACHED_FUNCTIONS[contractName][functionName];\n            if (!this.contractCallCache[contractName]) {\n                this.contractCallCache[contractName] = {};\n            }\n            switch (type) {\n                case CACHE_DATA_TYPES.NUMBER:\n                    this.contractCallCache[contractName][functionName] = Number(value);\n                    break;\n                default:\n                    this.contractCallCache[contractName][functionName] = value;\n            }\n        }\n    }\n\n    getContractCallCache(contractName, functionName) {\n        if (\n            CACHED_FUNCTIONS[contractName]?.[functionName] &&\n            this.contractCallCache[contractName]?.[functionName]\n        ) {\n            return this.contractCallCache[contractName][functionName];\n        }\n        return null;\n    }\n\n    initializeContract(contractName, contractAddress) {\n        if (ABIs[contractName] != null) {\n            this.contracts[contractName] = new ethers.Contract(\n                contractAddress,\n                ABIs[contractName],\n                this.operationalWallets[0],\n            );\n            this.contractAddresses[contractAddress] = this.contracts[contractName];\n        }\n    }\n\n    getContractAddress(contractName) {\n        const contract = this.contracts[contractName];\n\n        if (!contract) {\n            return null;\n        }\n\n        return contract.address;\n    }\n\n    async providerReady() {\n        return this.provider.getNetwork();\n    }\n\n    getPublicKeys() {\n        return this.operationalWallets.map((wallet) => wallet.address);\n    }\n\n    getManagementKey() {\n        return this.config.evmManagementWalletPublicKey;\n    }\n\n    async logBalances() {\n        for (const wallet of this.operationalWallets) {\n            // eslint-disable-next-line no-await-in-loop\n            const nativeBalance = await this.getNativeTokenBalance(wallet);\n            // eslint-disable-next-line no-await-in-loop\n            const tokenBalance = await this.getTokenBalance(wallet.address);\n            this.logger.info(\n                `Balance of ${wallet.address} is ${nativeBalance} ${this.baseTokenTicker} and ${tokenBalance} ${this.tracTicker}.`,\n            );\n        }\n    }\n\n    async getNativeTokenBalance(wallet) {\n        const nativeBalance = await wallet.getBalance();\n        return Number(ethers.utils.formatEther(nativeBalance));\n    }\n\n    async getTokenBalance(publicKey) {\n        const tokenBalance = await this.callContractFunction(this.contracts.Token, 'balanceOf', [\n            publicKey,\n        ]);\n        return Number(ethers.utils.formatEther(tokenBalance));\n    }\n\n    async getBlockNumber() {\n        const latestBlock = await this.provider.getBlock('latest');\n        return latestBlock.number;\n    }\n\n    async getIdentityId() {\n        if (this.identityId) {\n            return this.identityId;\n        }\n\n        const promises = this.operationalWallets.map((wallet) =>\n            this.callContractFunction(\n                this.contracts.IdentityStorage,\n                'getIdentityId',\n                [wallet.address],\n                CONTRACTS.IDENTITY_STORAGE,\n            ).then((identityId) => [wallet.address, Number(identityId)]),\n        );\n        const results = await Promise.all(promises);\n\n        this.identityId = 0;\n        const walletWithIdentityZero = [];\n        results.forEach(([publicKey, identityId]) => {\n            this.logger.trace(\n                `Identity id: ${identityId} found for wallet: ${publicKey} on blockchain: ${this.getBlockchainId()}`,\n            );\n            if (identityId !== 0) {\n                if (this.identityId !== identityId && this.identityId !== 0) {\n                    const index = this.operationalWallets.find(\n                        (wallet) => wallet.address === publicKey,\n                    );\n                    this.operationalWallets.splice(index, 1);\n                    this.logger.warn(\n                        `Found invalid identity id. Identity id: ${identityId} found for wallet: ${publicKey}, expected identity id: ${\n                            this.identityId\n                        } on blockchain: ${this.getBlockchainId()}. Operational wallet will not be used for transactions.`,\n                    );\n                    this.removeTransactionQueue(publicKey);\n                } else {\n                    this.identityId = identityId;\n                }\n            } else {\n                walletWithIdentityZero.push(publicKey);\n            }\n        });\n\n        if (this.identityId !== 0) {\n            walletWithIdentityZero.forEach((publicKey) => {\n                const index = this.operationalWallets.find(\n                    (wallet) => wallet.address === publicKey,\n                );\n                this.operationalWallets.splice(index, 1);\n                this.logger.warn(\n                    `Operational wallet: ${publicKey} don't have profile connected to it, expected identity id: ${\n                        this.identityId\n                    } on blockchain ${this.getBlockchainId()}`,\n                );\n            });\n        }\n\n        if (this.operationalWallets.length === 0) {\n            throw new Error(\n                `Unable to find valid operational wallets for blockchain implementation: ${this.getBlockchainId()}`,\n            );\n        }\n\n        return this.identityId;\n    }\n\n    async identityIdExists() {\n        const identityId = await this.getIdentityId();\n\n        return !!identityId;\n    }\n\n    async createProfile(peerId) {\n        if (!this.config.nodeName) {\n            throw new Error(\n                'Missing nodeName in blockchain configuration. Please add it and start the node again.',\n            );\n        }\n\n        const maxNumberOfRetries = 3;\n        let retryCount = 0;\n        let profileCreated = false;\n        const retryDelayInSec = 12;\n        while (retryCount + 1 <= maxNumberOfRetries && !profileCreated) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                await this._executeContractFunction(\n                    this.contracts.Profile,\n                    'createProfile',\n                    [\n                        this.getManagementKey(),\n                        this.getPublicKeys().slice(1),\n                        this.config.nodeName,\n                        ethers.utils.hexlify(ethers.utils.toUtf8Bytes(peerId)),\n                        this.config.operatorFee,\n                    ],\n                    null,\n                    this.operationalWallets[0],\n                );\n                this.logger.info(\n                    `Profile created with name: ${this.config.nodeName}, wallet: ${\n                        this.operationalWallets[0].address\n                    }, on blockchain ${this.getBlockchainId()}`,\n                );\n                profileCreated = true;\n            } catch (error) {\n                if (error.message.includes('Profile already exists')) {\n                    this.logger.info(\n                        `Skipping profile creation, already exists on blockchain ${this.getBlockchainId()}.`,\n                    );\n                    profileCreated = true;\n                } else if (retryCount + 1 < maxNumberOfRetries) {\n                    retryCount += 1;\n                    this.logger.warn(\n                        `Unable to create profile. Will retry in ${retryDelayInSec}s. Retries left: ${\n                            maxNumberOfRetries - retryCount\n                        } on blockchain ${this.getBlockchainId()}. Error: ${error}`,\n                    );\n                    // eslint-disable-next-line no-await-in-loop\n                    await sleep(retryDelayInSec * 1000);\n                } else {\n                    throw error;\n                }\n            }\n        }\n    }\n\n    async getGasPrice() {\n        try {\n            const response = await axios.get(this.config.gasPriceOracleLink);\n            const gasPriceRounded = Math.round(response.data.standard.maxFee * 1e9);\n            return gasPriceRounded;\n        } catch (error) {\n            return undefined;\n        }\n    }\n\n    buildTransactionGasParams(gasPrice) {\n        return { gasPrice };\n    }\n\n    async callContractFunction(contractInstance, functionName, args, contractName = null) {\n        const maxNumberOfRetries = 3;\n        const retryDelayInSec = 12;\n        let retryCount = 0;\n        let result = this.getContractCallCache(contractName, functionName);\n        try {\n            if (!result) {\n                while (retryCount < maxNumberOfRetries) {\n                    result = await contractInstance[functionName](...args);\n                    const resultIsValid = Web3ServiceValidator.validateResult(\n                        functionName,\n                        contractName,\n                        result,\n                        this.logger,\n                    );\n                    if (resultIsValid) {\n                        this.setContractCallCache(contractName, functionName, result);\n                        return result;\n                    }\n                    if (retryCount === maxNumberOfRetries - 1) {\n                        return null;\n                    }\n                    await sleep(retryDelayInSec * 1000);\n                    retryCount += 1;\n                }\n            }\n        } catch (error) {\n            this._decodeContractCallError(contractInstance, functionName, error, args);\n        }\n        return result;\n    }\n\n    async _executeContractFunction(\n        contractInstance,\n        functionName,\n        args,\n        predefinedGasPrice,\n        operationalWallet,\n    ) {\n        let result;\n        let gasPrice = predefinedGasPrice ?? (await this.getGasPrice());\n        let gasLimit;\n        let retryCount = 0;\n        const maxRetries = 3;\n\n        for (let estimateAttempt = 0; estimateAttempt < 3; estimateAttempt += 1) {\n            try {\n                /* eslint-disable no-await-in-loop */\n                gasLimit = await contractInstance.estimateGas[functionName](...args);\n                break;\n            } catch (error) {\n                const errMsg = error.message?.toLowerCase() ?? '';\n                const isTransient =\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.EXECUTION_FAILED.toLowerCase()) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.FEE_TOO_LOW.toLowerCase()) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.SOCKET_HANG_UP) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.ECONNRESET) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.ECONNREFUSED) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.SERVER_ERROR) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.BAD_GATEWAY) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.SERVICE_UNAVAILABLE) ||\n                    errMsg.includes(EXPECTED_TRANSACTION_ERRORS.EXPECT_BLOCK_NUMBER);\n                if (isTransient && estimateAttempt < 2) {\n                    this.logger.warn(\n                        `Gas estimation for ${functionName} failed with transient error on ${this.getBlockchainId()}, ` +\n                            `retrying (${estimateAttempt + 1}/3): ${error.message}`,\n                    );\n                    await new Promise((r) => {\n                        setTimeout(r, 2000);\n                    });\n                    continue;\n                }\n                this._decodeEstimateGasError(contractInstance, functionName, error, args);\n            }\n        }\n\n        gasLimit = gasLimit ?? ethers.utils.parseUnits('900', 'kwei');\n\n        const gasLimitMultiplier = CONTRACT_FUNCTION_GAS_LIMIT_INCREASE_FACTORS[functionName] ?? 1;\n\n        gasLimit = gasLimit.mul(gasLimitMultiplier * 100).div(100);\n\n        while (retryCount < maxRetries) {\n            try {\n                this.logger.debug(\n                    `Sending signed transaction ${functionName} to the blockchain ${this.getBlockchainId()}` +\n                        ` with gas limit: ${gasLimit.toString()} and gasPrice ${gasPrice.toString()}. ` +\n                        `Transaction queue length: ${this.getTotalTransactionQueueLength()}. Wallet used: ${\n                            operationalWallet.address\n                        }${retryCount > 0 ? ` (retry ${retryCount})` : ''}`,\n                );\n\n                const txOverrides = this.buildTransactionGasParams(gasPrice);\n                txOverrides.gasLimit = gasLimit;\n\n                const tx = await contractInstance\n                    .connect(operationalWallet)\n                    [functionName](...args, txOverrides);\n\n                try {\n                    result = await this.provider.waitForTransaction(\n                        tx.hash,\n                        TRANSACTION_CONFIRMATIONS,\n                        TRANSACTION_POLLING_TIMEOUT_MILLIS,\n                    );\n\n                    if (result.status === 0) {\n                        await this.provider.call(tx, tx.blockNumber);\n                    }\n                } catch (error) {\n                    if (\n                        error.message\n                            .toLowerCase()\n                            .includes(EXPECTED_TRANSACTION_ERRORS.TIMEOUT_EXCEEDED.toLowerCase())\n                    ) {\n                        const existingReceipt = await this.provider.getTransactionReceipt(tx.hash);\n                        if (existingReceipt) {\n                            this.logger.info(\n                                `Transaction ${functionName} (${tx.hash}) confirmed despite timeout. Block: ${existingReceipt.blockNumber}`,\n                            );\n                            if (existingReceipt.status === 0) {\n                                await this.provider.call(tx, existingReceipt.blockNumber);\n                            }\n                            return existingReceipt;\n                        }\n                        throw error;\n                    }\n                    this._decodeWaitForTxError(contractInstance, functionName, error, args);\n                }\n                return result;\n            } catch (error) {\n                const errorMessage = error.message.toLowerCase();\n\n                const isNonceError =\n                    errorMessage.includes(\n                        EXPECTED_TRANSACTION_ERRORS.NONCE_TOO_LOW.toLowerCase(),\n                    ) ||\n                    errorMessage.includes(\n                        EXPECTED_TRANSACTION_ERRORS.REPLACEMENT_UNDERPRICED.toLowerCase(),\n                    ) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.ALREADY_KNOWN.toLowerCase());\n\n                const isTimeoutError = errorMessage.includes(\n                    EXPECTED_TRANSACTION_ERRORS.TIMEOUT_EXCEEDED.toLowerCase(),\n                );\n\n                const isExecutionError =\n                    errorMessage.includes(\n                        EXPECTED_TRANSACTION_ERRORS.EXECUTION_FAILED.toLowerCase(),\n                    ) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.FEE_TOO_LOW.toLowerCase());\n\n                const isNetworkError =\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.SOCKET_HANG_UP) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.ECONNRESET) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.ECONNREFUSED) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.SERVER_ERROR) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.BAD_GATEWAY) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.SERVICE_UNAVAILABLE) ||\n                    errorMessage.includes(EXPECTED_TRANSACTION_ERRORS.EXPECT_BLOCK_NUMBER);\n\n                if (isNonceError || isTimeoutError || isExecutionError || isNetworkError) {\n                    retryCount += 1;\n                    if (retryCount < maxRetries) {\n                        const shouldBumpGas = isNonceError || isExecutionError;\n                        if (shouldBumpGas) {\n                            gasPrice = ethers.BigNumber.isBigNumber(gasPrice)\n                                ? gasPrice.mul(120).div(100)\n                                : Math.ceil(gasPrice * 1.2);\n                        }\n                        let errorType = 'Nonce';\n                        if (isTimeoutError) errorType = 'Timeout';\n                        else if (isExecutionError) errorType = 'Execution/fee';\n                        else if (isNetworkError) errorType = 'Network';\n                        this.logger.warn(\n                            `${errorType} error detected for ${functionName} on ${this.getBlockchainId()}. ` +\n                                `Retrying ${\n                                    shouldBumpGas\n                                        ? `with increased gas price: ${gasPrice}`\n                                        : 'with same gas price'\n                                } (retry ${retryCount}/${maxRetries})`,\n                        );\n                        continue;\n                    }\n                    let errorType = 'nonce';\n                    if (isTimeoutError) errorType = 'timeout';\n                    else if (isExecutionError) errorType = 'execution/fee';\n                    else if (isNetworkError) errorType = 'network';\n                    this.logger.error(\n                        `Max retries (${maxRetries}) reached for ${errorType} error in ${functionName} on ${this.getBlockchainId()}. ` +\n                            `Final gas price: ${gasPrice}`,\n                    );\n                }\n\n                throw error;\n            }\n        }\n    }\n\n    _decodeEstimateGasError(contractInstance, functionName, error, args) {\n        try {\n            const decodedErrorData = this._decodeErrorData(error, contractInstance.interface);\n\n            if (error.transaction === undefined) {\n                throw new Error(\n                    `Gas estimation for ${functionName} has failed, reason: ${decodedErrorData}`,\n                );\n            }\n\n            const functionFragment = contractInstance.interface.getFunction(\n                error.transaction.data.slice(0, 10),\n            );\n            const inputs = functionFragment.inputs\n                .map((input, i) => {\n                    const argName = input.name;\n                    const argValue = this._formatArgument(args[i]);\n                    return `${argName}=${argValue}`;\n                })\n                .join(', ');\n\n            throw new Error(\n                `Gas estimation for ${functionName}(${inputs}) has failed, reason: ${decodedErrorData}`,\n            );\n        } catch (decodeError) {\n            this.logger.warn(`Unable to decode estimate gas error: ${decodeError}`);\n            throw error;\n        }\n    }\n\n    _decodeWaitForTxError(contractInstance, functionName, error, args) {\n        try {\n            const decodedErrorData = this._decodeErrorData(error, contractInstance.interface);\n\n            let sigHash;\n            if (error.transaction) {\n                sigHash = error.transaction.data.slice(0, 10);\n            } else {\n                sigHash = this._getFunctionSighash(contractInstance, functionName, args);\n            }\n\n            const functionFragment = contractInstance.interface.getFunction(sigHash);\n            const inputs = functionFragment.inputs\n                .map((input, i) => {\n                    const argName = input.name;\n                    const argValue = this._formatArgument(args[i]);\n                    return `${argName}=${argValue}`;\n                })\n                .join(', ');\n\n            throw new Error(\n                `Transaction ${functionName}(${inputs}) has been reverted, reason: ${decodedErrorData}`,\n            );\n        } catch (decodeError) {\n            this.logger.warn(`Unable to decode wait for transaction error: ${decodeError}`);\n            throw error;\n        }\n    }\n\n    _decodeContractCallError(contractInstance, functionName, error, args) {\n        try {\n            const decodedErrorData = this._decodeErrorData(error, contractInstance.interface);\n\n            const functionFragment = contractInstance.interface.getFunction(\n                error.transaction.data.slice(0, 10),\n            );\n            const inputs = functionFragment.inputs\n                .map((input, i) => {\n                    const argName = input.name;\n                    const argValue = this._formatArgument(args[i]);\n                    return `${argName}=${argValue}`;\n                })\n                .join(', ');\n\n            throw new Error(`Call ${functionName}(${inputs}) failed, reason: ${decodedErrorData}`);\n        } catch (decodeError) {\n            this.logger.warn(`Unable to decode contract call error: ${decodeError}`);\n            throw error;\n        }\n    }\n\n    _getFunctionSighash(contractInstance, functionName, args) {\n        const functions = Object.keys(contractInstance.interface.functions)\n            .filter((key) => contractInstance.interface.functions[key].name === functionName)\n            .map((key) => ({ signature: key, ...contractInstance.interface.functions[key] }));\n\n        for (const func of functions) {\n            try {\n                // Checks if given arguments can be encoded with function ABI inputs\n                // may be useful for overloaded functions as it would help to find\n                // needed function fragment\n                ethers.utils.defaultAbiCoder.encode(func.inputs, args);\n\n                const sighash = ethers.utils.hexDataSlice(\n                    ethers.utils.keccak256(ethers.utils.toUtf8Bytes(func.signature)),\n                    0,\n                    4,\n                );\n\n                return sighash;\n            } catch (error) {\n                continue;\n            }\n        }\n\n        throw new Error('No matching function signature found');\n    }\n\n    _getErrorData(error) {\n        let nestedError = error;\n        while (nestedError && nestedError.error) {\n            nestedError = nestedError.error;\n        }\n        const errorData = nestedError.data;\n\n        if (errorData === undefined) {\n            throw error;\n        }\n\n        let returnData = typeof errorData === 'string' ? errorData : errorData.data;\n\n        if (typeof returnData === 'object' && returnData.data) {\n            returnData = returnData.data;\n        }\n\n        if (returnData === undefined || typeof returnData !== 'string') {\n            throw error;\n        }\n\n        return returnData;\n    }\n\n    _decodeInputData(inputData, contractInterface) {\n        if (inputData === ZERO_PREFIX) {\n            return 'Empty input data.';\n        }\n\n        return contractInterface.decodeFunctionData(inputData.slice(0, 10), inputData);\n    }\n\n    _decodeErrorData(evmError, contractInterface) {\n        let errorData;\n\n        try {\n            errorData = this._getErrorData(evmError);\n        } catch (error) {\n            return error.message;\n        }\n\n        // Handle empty error data\n        if (errorData === ZERO_PREFIX) {\n            return 'Empty error data.';\n        }\n\n        // Handle standard solidity string error\n        if (errorData.startsWith(SOLIDITY_ERROR_STRING_PREFIX)) {\n            const encodedReason = errorData.slice(SOLIDITY_ERROR_STRING_PREFIX.length);\n            try {\n                return ethers.utils.defaultAbiCoder.decode(['string'], `0x${encodedReason}`)[0];\n            } catch (error) {\n                return error.message;\n            }\n        }\n\n        // Handle solidity panic code\n        if (errorData.startsWith(SOLIDITY_PANIC_CODE_PREFIX)) {\n            const encodedReason = errorData.slice(SOLIDITY_PANIC_CODE_PREFIX.length);\n            let code;\n            try {\n                [code] = ethers.utils.defaultAbiCoder.decode(['uint256'], `0x${encodedReason}`);\n            } catch (error) {\n                return error.message;\n            }\n\n            return SOLIDITY_PANIC_REASONS[code] ?? 'Unknown Solidity panic code.';\n        }\n\n        // Try parsing a custom error using the contract ABI\n        try {\n            const decodedCustomError = contractInterface.parseError(errorData);\n            const formattedArgs = decodedCustomError.errorFragment.inputs\n                .map((input, i) => {\n                    const argName = input.name;\n                    const argValue = this._formatArgument(decodedCustomError.args[i]);\n                    return `${argName}=${argValue}`;\n                })\n                .join(', ');\n            return `custom error ${decodedCustomError.name}(${formattedArgs})`;\n        } catch (error) {\n            return `Failed to decode custom error data. Error: ${error}`;\n        }\n    }\n\n    _decodeResultData(fragment, resultData, contractInterface) {\n        if (resultData === ZERO_PREFIX) {\n            return 'Empty input data.';\n        }\n\n        return contractInterface.decodeFunctionResult(fragment, resultData);\n    }\n\n    _formatArgument(value) {\n        if (value === null || value === undefined) {\n            return 'null';\n        }\n\n        if (typeof value === 'string') {\n            return value;\n        }\n\n        if (typeof value === 'number' || BigNumber.isBigNumber(value)) {\n            return value.toString();\n        }\n\n        if (Array.isArray(value)) {\n            return `[${value.map((v) => this._formatArgument(v)).join(', ')}]`;\n        }\n\n        if (typeof value === 'object') {\n            const formattedEntries = Object.entries(value).map(\n                ([k, v]) => `${k}: ${this._formatArgument(v)}`,\n            );\n            return `{${formattedEntries.join(', ')}}`;\n        }\n\n        return value.toString();\n    }\n\n    async isAssetStorageContract(contractAddress) {\n        return this.callContractFunction(this.contracts.Hub, 'isAssetStorage(address)', [\n            contractAddress,\n        ]);\n    }\n\n    async getKnowledgeCollectionMerkleRootByIndex(\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n        index,\n    ) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n\n        return this.callContractFunction(assetStorageContractInstance, 'getMerkleRootByIndex', [\n            knowledgeCollectionId,\n            index,\n        ]);\n    }\n\n    async getKnowledgeCollectionLatestMerkleRoot(\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n    ) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n\n        return this.callContractFunction(assetStorageContractInstance, 'getLatestMerkleRoot', [\n            knowledgeCollectionId,\n        ]);\n    }\n\n    async getLatestKnowledgeCollectionId(assetStorageContractAddress) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n\n        const lastKnowledgeCollectionId = await this.callContractFunction(\n            assetStorageContractInstance,\n            'getLatestKnowledgeCollectionId',\n            [],\n        );\n        return lastKnowledgeCollectionId;\n    }\n\n    getAssetStorageContractAddresses() {\n        return Object.keys(this.assetStorageContracts);\n    }\n\n    async getKnowledgeCollectionMerkleRoots(assetStorageContractAddress, tokenId) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n\n        return this.callContractFunction(assetStorageContractInstance, 'getMerkleRoots', [tokenId]);\n    }\n\n    // async getKnowledgeAssetOwner(assetContractAddress, tokenId) {\n    //     const assetStorageContractInstance =\n    //         this.assetStorageContracts[assetContractAddress.toString().toLowerCase()];\n    //     if (!assetStorageContractInstance)\n    //         throw new Error('Unknown asset storage contract address');\n\n    //     return this.callContractFunction(assetStorageContractInstance, 'ownerOf', [tokenId]);\n    // }\n\n    async getLatestMerkleRootPublisher(assetStorageContractAddress, knowledgeCollectionId) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n        const knowledgeCollectionPublisher = await this.callContractFunction(\n            assetStorageContractInstance,\n            'getLatestMerkleRootPublisher',\n            [knowledgeCollectionId],\n        );\n        return knowledgeCollectionPublisher;\n    }\n\n    async getKnowledgeCollectionSize(assetStorageContractAddress, knowledgeCollectionId) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n        const knowledgeCollectionSize = await this.callContractFunction(\n            assetStorageContractInstance,\n            'getByteSize',\n            [knowledgeCollectionId],\n        );\n        return Number(knowledgeCollectionSize);\n    }\n\n    async getKnowledgeAssetsRange(assetStorageContractAddress, knowledgeCollectionId) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetStorageContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n        const knowledgeAssetsRange = await this.callContractFunction(\n            assetStorageContractInstance,\n            'getKnowledgeAssetsRange',\n            [knowledgeCollectionId],\n        );\n        return {\n            startTokenId: Number(\n                knowledgeAssetsRange[0]\n                    .sub(BigNumber.from(knowledgeCollectionId - 1).mul('0x0f4240'))\n                    .toString(),\n            ),\n            endTokenId: Number(\n                knowledgeAssetsRange[1]\n                    .sub(BigNumber.from(knowledgeCollectionId - 1).mul('0x0f4240'))\n                    .toString(),\n            ),\n            burned: knowledgeAssetsRange[2].map((burned) =>\n                Number(\n                    burned\n                        .sub(BigNumber.from(knowledgeCollectionId - 1).mul('0x0f4240'))\n                        .toString(),\n                ),\n            ),\n        };\n    }\n\n    async getMinimumStake() {\n        const minimumStake = await this.callContractFunction(\n            this.contracts.ParametersStorage,\n            'minimumStake',\n            [],\n            CONTRACTS.PARAMETERS_STORAGE,\n        );\n\n        return Number(ethers.utils.formatEther(minimumStake));\n    }\n\n    async getMaximumStake() {\n        const maximumStake = await this.callContractFunction(\n            this.contracts.ParametersStorage,\n            'maximumStake',\n            [],\n            CONTRACTS.PARAMETERS_STORAGE,\n        );\n\n        return Number(ethers.utils.formatEther(maximumStake));\n    }\n\n    async getMinimumRequiredSignatures() {\n        return this.callContractFunction(\n            this.contracts.ParametersStorage,\n            'minimumRequiredSignatures',\n            [],\n            CONTRACTS.PARAMETERS_STORAGE,\n        );\n    }\n\n    async getShardingTableHead() {\n        return this.callContractFunction(this.contracts.ShardingTableStorage, 'head', []);\n    }\n\n    async getShardingTableLength() {\n        const nodesCount = await this.callContractFunction(\n            this.contracts.ShardingTableStorage,\n            'nodesCount',\n            [],\n        );\n        return Number(nodesCount);\n    }\n\n    async getShardingTablePage(startingIdentityId, nodesNum) {\n        return this.callContractFunction(\n            this.contracts.ShardingTable,\n            'getShardingTable(uint72,uint72)',\n            [startingIdentityId, nodesNum],\n        );\n    }\n\n    getBlockchainId() {\n        return this.getImplementationName();\n    }\n\n    async healthCheck() {\n        try {\n            const gasPrice = await this.operationalWallets[0].getGasPrice();\n            if (gasPrice) return true;\n        } catch (e) {\n            this.logger.error(`Error on checking blockchain. ${e}`);\n            return false;\n        }\n        return false;\n    }\n\n    async restartService() {\n        await this.initializeWeb3();\n        await this.initializeContracts();\n    }\n\n    async getBlockchainTimestamp() {\n        return Math.floor(Date.now() / 1000);\n    }\n\n    async getLatestBlock() {\n        const currentBlock = await this.provider.getBlockNumber();\n        const blockTimestamp = await this.provider.getBlock(currentBlock);\n        return blockTimestamp;\n    }\n\n    async getParanetKnowledgeCollectionCount(paranetId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'getKnowledgeCollectionsCount',\n            [paranetId],\n        );\n    }\n\n    async getParanetKnowledgeCollectionLocatorsWithPagination(paranetId, offset, limit) {\n        return this.callContractFunction(\n            this.contracts.Paranet,\n            'getKnowledgeCollectionLocatorsWithPagination',\n            [paranetId, offset, limit],\n        );\n    }\n\n    async getParanetMetadata(paranetId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'getParanetMetadata',\n            [paranetId],\n            CONTRACTS.PARANETS_REGISTRY,\n        );\n    }\n\n    async getParanetName(paranetId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'getName',\n            [paranetId],\n            CONTRACTS.PARANETS_REGISTRY,\n        );\n    }\n\n    async getDescription(paranetId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'getDescription',\n            [paranetId],\n            CONTRACTS.PARANETS_REGISTRY,\n        );\n    }\n\n    async paranetExists(paranetId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'paranetExists',\n            [paranetId],\n            CONTRACTS.PARANETS_REGISTRY,\n        );\n    }\n\n    async isPermissionedNode(paranetId, identityId) {\n        return this.callContractFunction(this.contracts.ParanetsRegistry, 'isPermissionedNode', [\n            paranetId,\n            identityId,\n        ]);\n    }\n\n    async getNodesAccessPolicy(paranetId) {\n        return this.callContractFunction(this.contracts.ParanetsRegistry, 'getNodesAccessPolicy', [\n            paranetId,\n        ]);\n    }\n\n    async getPermissionedNodes(paranetId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'getPermissionedNodes',\n            [paranetId],\n            CONTRACTS.PARANETS_REGISTRY,\n        );\n    }\n\n    async getNodeId(identityId) {\n        return this.callContractFunction(this.contracts.ProfileStorage, 'getNodeId', [identityId]);\n    }\n\n    async signMessage(messageHash) {\n        const wallet = this.getRandomOperationalWallet();\n        return wallet.signMessage(ethers.utils.arrayify(messageHash));\n    }\n\n    async getStakeWeightedAverageAsk() {\n        return this.callContractFunction(\n            this.contracts.AskStorage,\n            'getStakeWeightedAverageAsk',\n            [],\n        );\n    }\n\n    async getTimeUntilNextEpoch() {\n        return this.callContractFunction(this.contracts.Chronos, 'timeUntilNextEpoch', []);\n    }\n\n    async getEpochLength() {\n        return this.callContractFunction(this.contracts.Chronos, 'epochLength', []);\n    }\n\n    async isKnowledgeCollectionRegistered(paranetId, knowledgeCollectionId) {\n        return this.callContractFunction(\n            this.contracts.ParanetsRegistry,\n            'isKnowledgeCollectionRegistered',\n            [paranetId, knowledgeCollectionId],\n        );\n    }\n\n    async getActiveProofPeriodStatus() {\n        return this.callContractFunction(\n            this.contracts.RandomSampling,\n            'getActiveProofPeriodStatus',\n            [],\n        );\n    }\n\n    async createChallenge() {\n        return new Promise((resolve) => {\n            this.queueTransaction(\n                this.contracts.RandomSampling,\n                'createChallenge',\n                [],\n                (result) => {\n                    if (result.error || result?.result?.status === 0) {\n                        resolve({\n                            success: false,\n                            error: result?.error ?? 'Error message not found',\n                        });\n                    } else {\n                        resolve({\n                            success: true,\n                            result: result.result,\n                        });\n                    }\n                },\n            );\n        });\n    }\n\n    async getNodeChallenge(nodeId) {\n        return this.callContractFunction(this.contracts.RandomSamplingStorage, 'getNodeChallenge', [\n            nodeId,\n        ]);\n    }\n\n    async submitProof(chunk, merkleProof) {\n        return new Promise((resolve, reject) => {\n            this.queueTransaction(\n                this.contracts.RandomSampling,\n                'submitProof',\n                [chunk, merkleProof],\n                (result) => {\n                    if (result.error) {\n                        reject(result.error);\n                    } else {\n                        resolve({\n                            success: true,\n                            result: result.result,\n                        });\n                    }\n                },\n            );\n        });\n    }\n\n    async getNodeEpochProofPeriodScore(nodeId, epoch, proofPeriodStartBlock) {\n        return this.callContractFunction(\n            this.contracts.RandomSamplingStorage,\n            'getNodeEpochProofPeriodScore',\n            [nodeId, epoch, proofPeriodStartBlock],\n        );\n    }\n\n    async getTransaction(txHash) {\n        return this.provider.getTransaction(txHash);\n    }\n\n    async getBlockTimestamp(blockNumber) {\n        const block = await this.provider.getBlock(blockNumber);\n        return block.timestamp;\n    }\n\n    async getDelegators(identityId) {\n        return this.callContractFunction(this.contracts.DelegatorsInfo, 'getDelegators', [\n            identityId,\n        ]);\n    }\n\n    async hasEverDelegated(identityId, address) {\n        return this.callContractFunction(this.contracts.DelegatorsInfo, 'hasEverDelegatedToNode', [\n            identityId,\n            address,\n        ]);\n    }\n\n    async getCurrentEpoch() {\n        return this.callContractFunction(this.contracts.Chronos, 'getCurrentEpoch', []);\n    }\n\n    async getLastClaimedEpoch(identityId, address) {\n        return this.callContractFunction(this.contracts.DelegatorsInfo, 'getLastClaimedEpoch', [\n            identityId,\n            address,\n        ]);\n    }\n\n    async batchClaimDelegatorRewards(identityId, epochs, delegators) {\n        return new Promise((resolve, reject) => {\n            this.queueTransaction(\n                this.contracts.Staking,\n                'batchClaimDelegatorRewards',\n                [identityId, epochs, delegators],\n                (result) => {\n                    if (result.error) {\n                        reject(result.error);\n                    } else {\n                        resolve({\n                            success: true,\n                            result: result.result,\n                        });\n                    }\n                },\n            );\n        });\n    }\n\n    async getAssetStorageContractsAddress() {\n        return Object.keys(this.assetStorageContracts);\n    }\n\n    // SUPPORT FOR OLD CONTRACTS\n    async getLatestAssertionId(assetContractAddress, tokenId) {\n        const assetStorageContractInstance =\n            this.assetStorageContracts[assetContractAddress.toString().toLowerCase()];\n        if (!assetStorageContractInstance)\n            throw new Error('Unknown asset storage contract address');\n\n        return this.callContractFunction(assetStorageContractInstance, 'getLatestAssertionId', [\n            tokenId,\n        ]);\n    }\n}\n\nexport default Web3Service;\n"
  },
  {
    "path": "src/modules/blockchain-events/blockchain-events-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass BlockchainEventsModuleManager extends BaseModuleManager {\n    getContractAddress(implementationName, blockchain, contractName) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getContractAddress(\n                blockchain,\n                contractName,\n            );\n        }\n    }\n\n    updateContractAddress(implementationName, blockchain, contractName, contractAddress) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.updateContractAddress(\n                blockchain,\n                contractName,\n                contractAddress,\n            );\n        }\n    }\n\n    async getBlock(implementationName, blockchain, tag) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getBlock(blockchain, tag);\n        }\n    }\n\n    async getPastEvents(\n        implementationName,\n        blockchain,\n        contractNames,\n        eventsToFilter,\n        lastCheckedBlock,\n        lastCheckedTimestamp,\n        currentBlock,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getPastEvents(\n                blockchain,\n                contractNames,\n                eventsToFilter,\n                lastCheckedBlock,\n                lastCheckedTimestamp,\n                currentBlock,\n            );\n        }\n    }\n\n    getName() {\n        return 'blockchainEvents';\n    }\n}\n\nexport default BlockchainEventsModuleManager;\n"
  },
  {
    "path": "src/modules/blockchain-events/implementation/blockchain-events-service.js",
    "content": "class BlockchainEventsService {\n    async initialize(config, logger) {\n        this.logger = logger;\n        this.config = config;\n    }\n\n    getContractAddress() {\n        throw Error('getContractAddress not implemented');\n    }\n\n    updateContractAddress() {\n        throw Error('updateContractAddress not implemented');\n    }\n\n    async getBlock() {\n        throw Error('getBlock not implemented');\n    }\n\n    async getPastEvents() {\n        throw Error('getPastEvents not implemented');\n    }\n}\n\nexport default BlockchainEventsService;\n"
  },
  {
    "path": "src/modules/blockchain-events/implementation/ot-ethers/ot-ethers.js",
    "content": "/* eslint-disable no-await-in-loop */\nimport { ethers } from 'ethers';\nimport BlockchainEventsService from '../blockchain-events-service.js';\n\nimport {\n    MAXIMUM_NUMBERS_OF_BLOCKS_TO_FETCH,\n    MAX_BLOCKCHAIN_EVENT_SYNC_OF_HISTORICAL_BLOCKS_IN_MILLS,\n    NODE_ENVIRONMENTS,\n    ABIs,\n    MONITORED_CONTRACTS,\n} from '../../../../constants/constants.js';\n\nclass OtEthers extends BlockchainEventsService {\n    async initialize(config, logger) {\n        await super.initialize(config, logger);\n        this.contractCallCache = {};\n        await this._initializeRpcProviders();\n        await this._initializeContracts();\n    }\n\n    async _initializeRpcProviders() {\n        this.providers = {};\n        for (const blockchain of this.config.blockchains) {\n            const validProviders = [];\n            for (const rpcEndpoint of this.config.rpcEndpoints[blockchain]) {\n                try {\n                    const provider = new ethers.providers.JsonRpcProvider(rpcEndpoint);\n\n                    // eslint-disable-next-line no-await-in-loop\n                    await provider.getNetwork();\n                    validProviders.push(provider);\n                } catch (error) {\n                    this.logger.error(\n                        `Failed to initialize provider: ${rpcEndpoint}. Error: ${error.message}`,\n                    );\n                }\n            }\n\n            if (validProviders.length === 0) {\n                throw new Error(`No valid providers found for blockchain: ${blockchain}`);\n            }\n\n            this.providers[blockchain] = validProviders;\n            this.logger.info(\n                `Initialized ${validProviders.length} valid providers for blockchain: ${blockchain}`,\n            );\n        }\n    }\n\n    _getRandomProvider(blockchain) {\n        const blockchainProviders = this.providers[blockchain];\n        if (!blockchainProviders || blockchainProviders.length === 0) {\n            throw new Error(`No providers available for blockchain: ${blockchain}`);\n        }\n        const randomIndex = Math.floor(Math.random() * blockchainProviders.length);\n        return blockchainProviders[randomIndex];\n    }\n\n    _getShuffledProviders(blockchain) {\n        const blockchainProviders = this.providers[blockchain];\n        if (!blockchainProviders || blockchainProviders.length === 0) {\n            throw new Error(`No providers available for blockchain: ${blockchain}`);\n        }\n        const shuffled = [...blockchainProviders];\n        for (let i = shuffled.length - 1; i > 0; i -= 1) {\n            const j = Math.floor(Math.random() * (i + 1));\n            [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];\n        }\n        return shuffled;\n    }\n\n    async _sendWithFailover(blockchain, method, params) {\n        const providers = this._getShuffledProviders(blockchain);\n        let lastError;\n        for (const provider of providers) {\n            try {\n                return await provider.send(method, params);\n            } catch (error) {\n                lastError = error;\n                this.logger.warn(\n                    `RPC provider failed for ${method} on ${blockchain}: ${error.message}. ` +\n                        `Trying next provider (${providers.indexOf(provider) + 1}/${\n                            providers.length\n                        })...`,\n                );\n            }\n        }\n        throw lastError;\n    }\n\n    async _initializeContracts() {\n        this.contracts = {};\n\n        for (const blockchain of this.config.blockchains) {\n            this.contracts[blockchain] = {};\n\n            this.logger.info(\n                `Initializing contracts with hub contract address: ${this.config.hubContractAddress[blockchain]}`,\n            );\n            this.contracts[blockchain].Hub = this.config.hubContractAddress[blockchain];\n\n            const provider = this._getRandomProvider(blockchain);\n            const hubContract = new ethers.Contract(\n                this.config.hubContractAddress[blockchain],\n                ABIs.Hub,\n                provider,\n            );\n\n            const contractsAray = await hubContract.getAllContracts();\n            const assetStoragesArray = await hubContract.getAllAssetStorages();\n\n            const allContracts = [...contractsAray, ...assetStoragesArray];\n\n            for (const [contractName, contractAddress] of allContracts) {\n                if (MONITORED_CONTRACTS.includes(contractName) && ABIs[contractName] != null) {\n                    this.contracts[blockchain][contractName] = contractAddress;\n                }\n            }\n        }\n    }\n\n    getContractAddress(blockchain, contractName) {\n        return this.contracts[blockchain][contractName];\n    }\n\n    updateContractAddress(blockchain, contractName, contractAddress) {\n        this.contracts[blockchain][contractName] = contractAddress;\n    }\n\n    async getBlock(blockchain, tag) {\n        const provider = this._getRandomProvider(blockchain);\n        return provider.getBlock(tag);\n    }\n\n    async getPastEvents(blockchain, contractNames, eventsToFilter, lastCheckedBlock, currentBlock) {\n        const maxBlocksToSync = await this._getMaxNumberOfHistoricalBlocksForSync(blockchain);\n        let fromBlock =\n            currentBlock - lastCheckedBlock > maxBlocksToSync ? currentBlock : lastCheckedBlock + 1;\n        const eventsMissed = currentBlock - lastCheckedBlock > maxBlocksToSync;\n        if (eventsMissed) {\n            return {\n                events: [],\n                lastCheckedBlock: currentBlock,\n                eventsMissed,\n            };\n        }\n        const contractAddresses = [];\n        const topics = [];\n        const addressToContractNameMap = {};\n\n        for (const contractName of contractNames) {\n            const contractAddress = this.contracts[blockchain][contractName];\n\n            if (!contractAddress) {\n                continue;\n            }\n\n            const provider = this._getRandomProvider(blockchain);\n            const contract = new ethers.Contract(contractAddress, ABIs[contractName], provider);\n            const contractTopics = [];\n            for (const filterName in contract.filters) {\n                if (!eventsToFilter.includes(filterName)) {\n                    continue;\n                }\n                const filter = contract.filters[filterName]().topics[0];\n                contractTopics.push(filter);\n            }\n\n            if (contractTopics.length > 0) {\n                contractAddresses.push(contract.address);\n                topics.push(...contractTopics);\n                addressToContractNameMap[contract.address.toLowerCase()] = contractName;\n            }\n        }\n\n        const events = [];\n        let toBlock = currentBlock;\n        try {\n            while (fromBlock <= currentBlock) {\n                toBlock = Math.min(\n                    fromBlock + MAXIMUM_NUMBERS_OF_BLOCKS_TO_FETCH - 1,\n                    currentBlock,\n                );\n\n                const fromBlockParam = ethers.BigNumber.from(fromBlock)\n                    .toHexString()\n                    .replace(/^0x0+/, '0x');\n                const toBlockParam = ethers.BigNumber.from(toBlock)\n                    .toHexString()\n                    .replace(/^0x0+/, '0x');\n                const newLogs = await this._sendWithFailover(blockchain, 'eth_getLogs', [\n                    {\n                        address: contractAddresses,\n                        fromBlock: fromBlockParam,\n                        toBlock: toBlockParam,\n                        topics: [topics],\n                    },\n                ]);\n                for (const log of newLogs) {\n                    const contractName = addressToContractNameMap[log.address];\n                    const contractInterface = new ethers.utils.Interface(ABIs[contractName]);\n\n                    try {\n                        const parsedLog = contractInterface.parseLog(log);\n                        events.push({\n                            blockchain,\n                            contract: contractName,\n                            contractAddress: log.address,\n                            event: parsedLog.name,\n                            data: JSON.stringify(\n                                Object.fromEntries(\n                                    Object.entries(parsedLog.args).map(([k, v]) => [\n                                        k,\n                                        ethers.BigNumber.isBigNumber(v) ? v.toString() : v,\n                                    ]),\n                                ),\n                            ),\n                            blockNumber: parseInt(log.blockNumber, 16),\n                            transactionIndex: parseInt(log.transactionIndex, 16),\n                            logIndex: parseInt(log.logIndex, 16),\n                            txHash: log.transactionHash,\n                        });\n                    } catch (error) {\n                        this.logger.warn(\n                            `Failed to parse log for contract: ${contractName}. Error: ${error.message}`,\n                        );\n                    }\n                }\n\n                fromBlock = toBlock + 1;\n            }\n        } catch (error) {\n            this.logger.warn(\n                `Unable to process block range from: ${fromBlock} to: ${toBlock} on blockchain: ${blockchain}. Error: ${error.message}`,\n            );\n        }\n\n        return {\n            events,\n            eventsMissed,\n        };\n    }\n\n    async _getMaxNumberOfHistoricalBlocksForSync(blockchain) {\n        if (!this.maxNumberOfHistoricalBlocksForSync) {\n            if (\n                [NODE_ENVIRONMENTS.DEVELOPMENT, NODE_ENVIRONMENTS.TEST].includes(\n                    process.env.NODE_ENV,\n                )\n            ) {\n                this.maxNumberOfHistoricalBlocksForSync = Infinity;\n            } else {\n                const blockTimeMillis = await this._getBlockTimeMillis(blockchain);\n\n                this.maxNumberOfHistoricalBlocksForSync = Math.round(\n                    // 60 * 60 * 1000 = 1 hour\n                    MAX_BLOCKCHAIN_EVENT_SYNC_OF_HISTORICAL_BLOCKS_IN_MILLS / blockTimeMillis,\n                );\n            }\n        }\n        return this.maxNumberOfHistoricalBlocksForSync;\n    }\n\n    async _getBlockTimeMillis(blockchain, blockRange = 1000) {\n        const latestBlock = await this.getBlock(blockchain);\n        const olderBlock = await this.getBlock(blockchain, latestBlock.number - blockRange);\n\n        const timeDiffMillis = (latestBlock.timestamp - olderBlock.timestamp) * 1000;\n        return timeDiffMillis / blockRange;\n    }\n}\n\nexport default OtEthers;\n"
  },
  {
    "path": "src/modules/http-client/http-client-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass HttpClientModuleManager extends BaseModuleManager {\n    constructor(ctx) {\n        super(ctx);\n        this.authService = ctx.authService;\n    }\n\n    getName() {\n        return 'httpClient';\n    }\n\n    get(route, callback, options = {}) {\n        if (this.initialized) {\n            return this.getImplementation().module.get(route, callback, options);\n        }\n    }\n\n    post(route, callback, options = {}) {\n        if (this.initialized) {\n            return this.getImplementation().module.post(route, callback, options);\n        }\n    }\n\n    use(route, callback, options = {}) {\n        if (this.initialized) {\n            return this.getImplementation().module.use(route, callback, options);\n        }\n    }\n\n    createRouterInstance() {\n        if (this.initialized) {\n            return this.getImplementation().module.createRouterInstance();\n        }\n    }\n\n    sendResponse(res, status, returnObject) {\n        if (this.initialized) {\n            return this.getImplementation().module.sendResponse(res, status, returnObject);\n        }\n    }\n\n    async listen() {\n        if (this.initialized) {\n            return this.getImplementation().module.listen();\n        }\n    }\n\n    selectMiddlewares(options) {\n        if (this.initialized) {\n            return this.getImplementation().module.selectMiddlewares(options);\n        }\n    }\n\n    initializeBeforeMiddlewares(blockchainImpelemntations) {\n        if (this.initialized) {\n            return this.getImplementation().module.initializeBeforeMiddlewares(\n                this.authService,\n                blockchainImpelemntations,\n            );\n        }\n    }\n\n    initializeAfterMiddlewares() {\n        if (this.initialized) {\n            return this.getImplementation().module.initializeAfterMiddlewares(this.authService);\n        }\n    }\n}\n\nexport default HttpClientModuleManager;\n"
  },
  {
    "path": "src/modules/http-client/implementation/express-http-client.js",
    "content": "import express from 'express';\nimport https from 'https';\nimport fs from 'fs-extra';\nimport fileUpload from 'express-fileupload';\nimport cors from 'cors';\nimport requestValidationMiddleware from './middleware/request-validation-middleware.js';\nimport rateLimiterMiddleware from './middleware/rate-limiter-middleware.js';\nimport authenticationMiddleware from './middleware/authentication-middleware.js';\nimport authorizationMiddleware from './middleware/authorization-middleware.js';\nimport blockchainIdMiddleware from './middleware/blockchain-id-midleware.js';\nimport { BYTES_IN_MEGABYTE, MAX_FILE_SIZE } from '../../../constants/constants.js';\n\nclass ExpressHttpClient {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n        this.app = express();\n    }\n\n    get(route, callback, options) {\n        this.app.get(route, ...this.selectMiddlewares(options), callback);\n    }\n\n    post(route, callback, options) {\n        this.app.post(route, ...this.selectMiddlewares(options), callback);\n    }\n\n    use(route, callback, options) {\n        this.app.use(route, ...this.selectMiddlewares(options), callback);\n    }\n\n    createRouterInstance() {\n        return express.Router();\n    }\n\n    sendResponse(res, status, returnObject) {\n        res.status(status);\n        res.send(returnObject);\n    }\n\n    async listen() {\n        if (this.config.useSsl) {\n            const [key, cert] = await Promise.all([\n                fs.promises.readFile(this.config.sslKeyPath),\n                fs.promises.readFile(this.config.sslCertificatePath),\n            ]);\n            this.httpsServer = https.createServer(\n                {\n                    key,\n                    cert,\n                },\n                this.app,\n            );\n            this.httpsServer.listen(this.config.port);\n        } else {\n            this.app.listen(this.config.port);\n        }\n        this.logger.info(`Node listening on port: ${this.config.port}`);\n    }\n\n    selectMiddlewares(options) {\n        const middlewares = [];\n        if (options.rateLimit) middlewares.push(rateLimiterMiddleware(this.config.rateLimiter));\n        if (options.requestSchema)\n            middlewares.push(requestValidationMiddleware(options.requestSchema));\n\n        return middlewares;\n    }\n\n    initializeBeforeMiddlewares(authService, blockchainImplementations) {\n        this._initializeCorsMiddleware();\n        this.app.use(authenticationMiddleware(authService));\n        this.app.use(authorizationMiddleware(authService));\n        this._initializeBaseMiddlewares();\n        this.app.use(blockchainIdMiddleware(blockchainImplementations));\n    }\n\n    initializeAfterMiddlewares() {\n        // placeholder method for after middlewares\n    }\n\n    _initializeCorsMiddleware() {\n        const corsOptions = {};\n\n        if (this.config.auth?.cors?.allowedOrigin) {\n            corsOptions.origin = this.config.auth.cors.allowedOrigin;\n        }\n\n        this.app.use(cors(corsOptions));\n    }\n\n    _initializeBaseMiddlewares() {\n        this.app.use(\n            fileUpload({\n                createParentPath: true,\n            }),\n        );\n\n        this.app.use(express.json({ limit: `${MAX_FILE_SIZE / BYTES_IN_MEGABYTE}mb` }));\n        this.app.use((req, res, next) => {\n            this.logger.api(`${req.method}: ${req.url} request received`);\n            return next();\n        });\n    }\n}\n\nexport default ExpressHttpClient;\n"
  },
  {
    "path": "src/modules/http-client/implementation/middleware/authentication-middleware.js",
    "content": "const parseIp = (req) => {\n    let xForwardedFor;\n    let socketRemoteAddress;\n\n    if (req.socket) {\n        socketRemoteAddress = req.socket.remoteAddress;\n    }\n\n    return xForwardedFor || socketRemoteAddress;\n};\n\nexport default (authService) => async (req, res, next) => {\n    // eslint-disable-next-line no-useless-escape\n    const match = req.path.match(/^\\/(?:v[0-9]+\\/)?([^\\/\\?]+)/);\n    if (!match) return res.status(404).send('Not found.');\n\n    const operation = match[0].substring(1).toUpperCase();\n\n    if (authService.isPublicOperation(operation)) {\n        return next();\n    }\n\n    const ip = parseIp(req);\n\n    const token =\n        req.headers.authorization &&\n        req.headers.authorization.startsWith('Bearer ') &&\n        req.headers.authorization.split(' ')[1];\n\n    const isAuthenticated = await authService.authenticate(ip, token);\n\n    if (!isAuthenticated) {\n        return res.status(401).send('Unauthenticated.');\n    }\n\n    next();\n};\n"
  },
  {
    "path": "src/modules/http-client/implementation/middleware/authorization-middleware.js",
    "content": "const getToken = (req) => {\n    if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {\n        return req.headers.authorization.split(' ')[1];\n    }\n};\n\nexport default (authService) => async (req, res, next) => {\n    // eslint-disable-next-line no-useless-escape\n    const match = req.path.match(/^\\/(?:v[0-9]+\\/)?([^\\/\\?]+)/);\n    if (!match) return res.status(404).send('Not found.');\n\n    const operation = match[0].substring(1).toUpperCase();\n\n    if (authService.isPublicOperation(operation)) {\n        return next();\n    }\n\n    const token = getToken(req);\n    const isAuthorized = await authService.isAuthorized(token, operation);\n\n    if (!isAuthorized) {\n        return res.status(403).send('Forbidden.');\n    }\n\n    next();\n};\n"
  },
  {
    "path": "src/modules/http-client/implementation/middleware/blockchain-id-midleware.js",
    "content": "function addBlockchainId(blockchain, blockchainImplementations) {\n    let updatedBlockchain = blockchain;\n\n    if (blockchain?.split(':').length === 1) {\n        for (const implementation of blockchainImplementations) {\n            if (implementation.split(':')[0] === blockchain) {\n                updatedBlockchain = implementation;\n                break;\n            }\n        }\n    }\n\n    return updatedBlockchain;\n}\n\nexport default function blockchainIdMiddleware(blockchainImplementations) {\n    return (req, res, next) => {\n        if (req.method === 'GET')\n            req.query.blockchain = addBlockchainId(req.query.blockchain, blockchainImplementations);\n        else if (Array.isArray(req.body)) {\n            for (const element of req.body) {\n                element.blockchain = addBlockchainId(element.blockchain, blockchainImplementations);\n            }\n        } else {\n            req.body.blockchain = addBlockchainId(req.body.blockchain, blockchainImplementations);\n        }\n\n        next();\n    };\n}\n"
  },
  {
    "path": "src/modules/http-client/implementation/middleware/rate-limiter-middleware.js",
    "content": "import rateLimiter from 'express-rate-limit';\n\nexport default (config) =>\n    rateLimiter({\n        windowMs: config.timeWindowSeconds * 1000,\n        max: config.maxRequests,\n        message: `Too many requests sent, maximum number of requests per minute is ${config.maxRequests}`,\n        standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers\n        legacyHeaders: false, // Disable the `X-RateLimit-*` headers\n    });\n"
  },
  {
    "path": "src/modules/http-client/implementation/middleware/request-validation-middleware.js",
    "content": "import { Validator } from 'jsonschema';\n\nconst v = new Validator();\n\nfunction preValidateProperty(object, key, schema, options, ctx) {\n    const value = object[key];\n    if (typeof value === 'undefined') return;\n\n    // Test if the schema declares a type, but the type keyword fails validation\n    if (\n        schema.type &&\n        v.attributes.type.call(v, value, schema, options, ctx.makeChild(schema, key))\n    ) {\n        // If the type is \"number\" but the instance is not a number, cast it\n        if (schema.type === 'number' && typeof value !== 'number') {\n            // eslint-disable-next-line no-param-reassign\n            object[key] = parseFloat(value);\n        }\n    }\n}\n\nexport default function requestValidationMiddleware(requestSchema) {\n    return (req, res, next) => {\n        let result;\n        if (req.method === 'GET')\n            result = v.validate(req.query, requestSchema, { preValidateProperty });\n        else if (req.get('Content-Type') !== 'application/json') {\n            res.status(401).send('Invalid header format');\n            return;\n        } else result = v.validate(req.body, requestSchema);\n\n        if (result.errors.length > 0) {\n            res.status(400).json({\n                status: 'FAILED',\n                errors: result.errors.map((e) => e.message),\n            });\n        } else {\n            next();\n        }\n    };\n}\n"
  },
  {
    "path": "src/modules/module-config-validation.js",
    "content": "import { REQUIRED_MODULES, TRIPLE_STORE_REPOSITORIES } from '../constants/constants.js';\n\nclass ModuleConfigValidation {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n    }\n\n    validateModule(name, config) {\n        this.validateRequiredModule(name, config);\n        const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);\n        if (typeof this[`validate${capitalizedName}`] === 'function') {\n            this[`validate${capitalizedName}`](config);\n        } else {\n            throw new Error(`Missing validation for ${capitalizedName}`);\n        }\n    }\n\n    validateAutoUpdater() {\n        return true;\n    }\n\n    validateBlockchain() {\n        return true;\n    }\n\n    validateHttpClient() {\n        return true;\n    }\n\n    validateNetwork() {\n        return true;\n    }\n\n    validateRepository() {\n        return true;\n    }\n\n    validateBlockchainEvents(config) {\n        const occurences = {};\n        for (const implementation of Object.values(config.implementation)) {\n            // eslint-disable-next-line no-continue\n            if (!implementation.enabled) {\n                continue;\n            }\n\n            if (implementation.config.blockchains.length === 0) {\n                throw new Error(\n                    'Blockchains must be specified in the blockchain events service config.',\n                );\n            }\n\n            if (\n                implementation.config.blockchains.length >\n                Object.keys(implementation.config.rpcEndpoints).length\n            ) {\n                throw new Error('Missing RPC edpoints in the blockchain events service config.');\n            }\n\n            if (\n                implementation.config.blockchains.length >\n                Object.keys(implementation.config.hubContractAddress).length\n            ) {\n                throw new Error('Missing hub addresses in the blockchain events service config.');\n            }\n\n            for (const blockchain of implementation.config.blockchains) {\n                if (!occurences[blockchain]) {\n                    occurences[blockchain] = 0;\n                }\n                occurences[blockchain] += 1;\n\n                if (occurences[blockchain] > 1) {\n                    throw new Error(\n                        `Exactly one blockchain events service for blockchain ${blockchain} needs to be defined.`,\n                    );\n                }\n\n                if (\n                    !implementation.config.rpcEndpoints[blockchain] ||\n                    implementation.config.rpcEndpoints[blockchain].length === 0\n                ) {\n                    throw new Error(\n                        `RPC endpoint is not defined for blockchain: ${blockchain} in the blockchain events service config.`,\n                    );\n                }\n\n                if (!implementation.config.hubContractAddress[blockchain]) {\n                    throw new Error(\n                        `Hub contract address is not defined for blockchain: ${blockchain} in the blockchain events service config.`,\n                    );\n                }\n            }\n        }\n    }\n\n    validateTripleStore(config) {\n        const occurences = {};\n        for (const implementation of Object.values(config.implementation)) {\n            // eslint-disable-next-line no-continue\n            if (!implementation.enabled) {\n                continue;\n            }\n\n            for (const repository in implementation.config.repositories) {\n                if (!occurences[repository]) {\n                    occurences[repository] = 0;\n                }\n                occurences[repository] += 1;\n            }\n        }\n        for (const repository of Object.values(TRIPLE_STORE_REPOSITORIES)) {\n            if (occurences[repository] !== 1) {\n                throw new Error(\n                    `Exactly one config for repository ${repository} needs to be defined.`,\n                );\n            }\n        }\n    }\n\n    validateValidation() {\n        return true;\n    }\n\n    validateRequiredModule(moduleName, moduleConfig) {\n        if (\n            !moduleConfig?.enabled ||\n            !Object.values(moduleConfig.implementation).filter(\n                (implementationConfig) => implementationConfig.enabled,\n            ).length\n        ) {\n            const message = `${moduleName} module not defined or enabled in configuration`;\n            if (REQUIRED_MODULES.includes(moduleName)) {\n                throw new Error(`${message} but it's required!`);\n            }\n            this.logger.warn(message);\n        }\n    }\n\n    validateTelemetry() {\n        return true;\n    }\n}\n\nexport default ModuleConfigValidation;\n"
  },
  {
    "path": "src/modules/network/implementation/libp2p-service.js",
    "content": "import appRootPath from 'app-root-path';\nimport libp2p from 'libp2p';\nimport KadDHT from 'libp2p-kad-dht';\nimport { join } from 'path';\nimport Bootstrap, { tag } from 'libp2p-bootstrap';\nimport { NOISE } from 'libp2p-noise';\nimport MPLEX from 'libp2p-mplex';\nimport TCP from 'libp2p-tcp';\nimport pipe from 'it-pipe';\nimport map from 'it-map';\nimport { encode, decode } from 'it-length-prefixed';\nimport { create as _create, createFromPrivKey, createFromB58String } from 'peer-id';\nimport { InMemoryRateLimiter } from 'rolling-rate-limiter';\nimport toobusy from 'toobusy-js';\nimport { mkdir, writeFile, readFile, stat } from 'fs/promises';\nimport ip from 'ip';\nimport { TimeoutController } from 'timeout-abort-controller';\nimport {\n    NETWORK_API_RATE_LIMIT,\n    NETWORK_API_SPAM_DETECTION,\n    NETWORK_MESSAGE_TYPES,\n    NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES,\n    LIBP2P_KEY_DIRECTORY,\n    LIBP2P_KEY_FILENAME,\n    NODE_ENVIRONMENTS,\n    BYTES_IN_MEGABYTE,\n} from '../../../constants/constants.js';\n\nconst devEnvironment =\n    process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVELOPMENT ||\n    process.env.NODE_ENV === NODE_ENVIRONMENTS.TEST;\n\nconst initializationObject = {\n    addresses: {\n        listen: ['/ip4/0.0.0.0/tcp/9000'],\n    },\n    modules: {\n        transport: [TCP],\n        streamMuxer: [MPLEX],\n        connEncryption: [NOISE],\n        dht: KadDHT,\n    },\n};\n\nclass Libp2pService {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n\n        initializationObject.peerRouting = this.config.peerRouting;\n\n        const externalIp =\n            ip.isV4Format(this.config.nat.externalIp) && ip.isPublic(this.config.nat.externalIp)\n                ? this.config.nat.externalIp\n                : undefined;\n\n        if (this.config.nat.externalIp != null && externalIp == null) {\n            this.logger.warn(\n                `Invalid external ip defined in configuration: ${this.config.nat.externalIp}. External ip must be in V4 format, and public.`,\n            );\n        }\n\n        initializationObject.config = {\n            dht: {\n                enabled: true,\n                ...this.config.dht,\n            },\n            nat: {\n                ...this.config.nat,\n                externalIp,\n            },\n        };\n        initializationObject.dialer = this.config.connectionManager;\n\n        if (this.config.bootstrap.length > 0) {\n            initializationObject.modules.peerDiscovery = [Bootstrap];\n            initializationObject.config.peerDiscovery = {\n                autoDial: true,\n                [tag]: {\n                    enabled: true,\n                    list: this.config.bootstrap,\n                },\n            };\n        }\n        initializationObject.addresses = {\n            listen: [`/ip4/0.0.0.0/tcp/${this.config.port}`],\n            announce: externalIp ? [`/ip4/${externalIp}/tcp/${this.config.port}`] : [],\n        };\n        let id;\n        if (!this.config.peerId) {\n            if (!devEnvironment || !this.config.privateKey) {\n                this.config.privateKey = await this.readPrivateKeyFromFile();\n            }\n\n            if (!this.config.privateKey) {\n                id = await _create({ bits: 1024, keyType: 'RSA' });\n                this.config.privateKey = id.toJSON().privKey;\n                await this.savePrivateKeyInFile(this.config.privateKey);\n            } else {\n                id = await createFromPrivKey(this.config.privateKey);\n            }\n            this.config.peerId = id;\n        }\n\n        initializationObject.peerId = this.config.peerId;\n        this._initializeRateLimiters();\n        this.sessions = {};\n        this.node = await libp2p.create(initializationObject);\n        const peerId = this.node.peerId.toB58String();\n        this.config.id = peerId;\n    }\n\n    async start() {\n        await this.node.start();\n        const port = parseInt(this.node.multiaddrs.toString().split('/')[4], 10);\n        this.logger.info(`Network ID is ${this.config.id}, connection port is ${port}`);\n    }\n\n    async onPeerConnected(listener) {\n        this.node.connectionManager.on('peer:connect', listener);\n    }\n\n    async savePrivateKeyInFile(privateKey) {\n        const { fullPath, directoryPath } = this.getKeyPath();\n        await mkdir(directoryPath, { recursive: true });\n        await writeFile(fullPath, privateKey);\n    }\n\n    getKeyPath() {\n        let directoryPath;\n        if (!devEnvironment) {\n            directoryPath = join(\n                appRootPath.path,\n                '..',\n                this.config.appDataPath,\n                LIBP2P_KEY_DIRECTORY,\n            );\n        } else {\n            directoryPath = join(appRootPath.path, this.config.appDataPath, LIBP2P_KEY_DIRECTORY);\n        }\n\n        const fullPath = join(directoryPath, LIBP2P_KEY_FILENAME);\n        return { fullPath, directoryPath };\n    }\n\n    async readPrivateKeyFromFile() {\n        const keyPath = this.getKeyPath();\n        if (await this.fileExists(keyPath.fullPath)) {\n            const key = (await readFile(keyPath.fullPath)).toString();\n            return key;\n        }\n    }\n\n    async fileExists(filePath) {\n        try {\n            await stat(filePath);\n            return true;\n        } catch (e) {\n            return false;\n        }\n    }\n\n    _initializeRateLimiters() {\n        const basicRateLimiter = new InMemoryRateLimiter({\n            interval: NETWORK_API_RATE_LIMIT.TIME_WINDOW_MILLS,\n            maxInInterval: NETWORK_API_RATE_LIMIT.MAX_NUMBER,\n        });\n\n        const spamDetection = new InMemoryRateLimiter({\n            interval: NETWORK_API_SPAM_DETECTION.TIME_WINDOW_MILLS,\n            maxInInterval: NETWORK_API_SPAM_DETECTION.MAX_NUMBER,\n        });\n\n        this.rateLimiter = {\n            basicRateLimiter,\n            spamDetection,\n        };\n\n        this.blackList = {};\n    }\n\n    getMultiaddrs() {\n        return this.node.multiaddrs;\n    }\n\n    getProtocols(peerIdObject) {\n        return this.node.peerStore.protoBook.get(peerIdObject);\n    }\n\n    getAddresses(peerIdObject) {\n        return this.node.peerStore.addressBook.get(peerIdObject);\n    }\n\n    getPeers() {\n        return this.node.connectionManager.connections;\n    }\n\n    getPeerId() {\n        return this.node.peerId;\n    }\n\n    handleMessage(protocol, handler) {\n        this.logger.info(`Enabling network protocol: ${protocol}`);\n\n        this.node.handle(protocol, async (handlerProps) => {\n            const { stream } = handlerProps;\n            const peerIdString = handlerProps.connection.remotePeer.toB58String();\n            const { message, valid, busy } = await this._readMessageFromStream(\n                stream,\n                this.isRequestValid.bind(this),\n                peerIdString,\n            );\n\n            this.updateSessionStream(message.header.operationId, peerIdString, stream);\n\n            if (!valid) {\n                await this.sendMessageResponse(\n                    protocol,\n                    peerIdString,\n                    NETWORK_MESSAGE_TYPES.RESPONSES.NACK,\n                    message.header.operationId,\n                    { errorMessage: 'Invalid request message' },\n                );\n                this.removeCachedSession(message.header.operationId, peerIdString);\n            } else if (busy) {\n                await this.sendMessageResponse(\n                    protocol,\n                    peerIdString,\n                    NETWORK_MESSAGE_TYPES.RESPONSES.BUSY,\n                    message.header.operationId,\n                    {},\n                );\n                this.removeCachedSession(message.header.operationId, peerIdString);\n            } else {\n                this.logger.debug(\n                    `Receiving message from ${peerIdString} to ${this.config.id}: protocol: ${protocol}, messageType: ${message.header.messageType};`,\n                );\n                await handler(message, peerIdString);\n            }\n        });\n    }\n\n    updateSessionStream(operationId, peerIdString, stream) {\n        this.logger.trace(\n            `Storing new session stream for remotePeerId: ${peerIdString} with operation id: ${operationId}`,\n        );\n        if (!this.sessions[peerIdString]) {\n            this.sessions[peerIdString] = {\n                [operationId]: {\n                    stream,\n                },\n            };\n        } else if (!this.sessions[peerIdString][operationId]) {\n            this.sessions[peerIdString][operationId] = {\n                stream,\n            };\n        } else {\n            this.sessions[peerIdString][operationId] = {\n                stream,\n            };\n        }\n    }\n\n    getSessionStream(operationId, peerIdString) {\n        if (this.sessions[peerIdString] && this.sessions[peerIdString][operationId]) {\n            this.logger.trace(\n                `Session found remotePeerId: ${peerIdString}, operation id: ${operationId}`,\n            );\n            return this.sessions[peerIdString][operationId].stream;\n        }\n        return null;\n    }\n\n    createStreamMessage(message, operationId, messageType) {\n        return {\n            header: {\n                messageType,\n                operationId,\n            },\n            data: message,\n        };\n    }\n\n    async sendMessage(protocol, peerIdString, messageType, operationId, message, timeout) {\n        const nackMessage = {\n            header: { messageType: NETWORK_MESSAGE_TYPES.RESPONSES.NACK },\n            data: {\n                errorMessage: '',\n            },\n        };\n\n        const peerIdObject = createFromB58String(peerIdString);\n\n        const publicIp = (this.getAddresses(peerIdObject) ?? [])\n            .map((addr) => addr.multiaddr)\n            .filter((addr) => addr.isThinWaistAddress())\n            .map((addr) => addr.toString().split('/'))\n            .filter((splittedAddr) => !ip.isPrivate(splittedAddr[2]))[0]?.[2];\n\n        this.logger.trace(\n            `Dialing remotePeerId: ${peerIdString} with public ip: ${publicIp}: protocol: ${protocol}, messageType: ${messageType} , operationId: ${operationId}`,\n        );\n        let dialResult;\n        let dialStart;\n        let dialEnd;\n        try {\n            dialStart = Date.now();\n            dialResult = await this.node.dialProtocol(peerIdObject, protocol);\n            dialEnd = Date.now();\n        } catch (error) {\n            dialEnd = Date.now();\n            nackMessage.data.errorMessage = `Unable to dial peer: ${peerIdString}. protocol: ${protocol}, messageType: ${messageType} , operationId: ${operationId}, dial execution time: ${\n                dialEnd - dialStart\n            } ms. Error: ${error.message}`;\n\n            return nackMessage;\n        }\n        this.logger.trace(\n            `Created stream for peer: ${peerIdString}. protocol: ${protocol}, messageType: ${messageType} , operationId: ${operationId}, dial execution time: ${\n                dialEnd - dialStart\n            } ms.`,\n        );\n\n        const { stream } = dialResult;\n\n        this.updateSessionStream(operationId, peerIdString, stream);\n\n        const streamMessage = this.createStreamMessage(message, operationId, messageType);\n\n        this.logger.trace(\n            `Sending message to ${peerIdString}. protocol: ${protocol}, messageType: ${messageType}, operationId: ${operationId}`,\n        );\n\n        let sendMessageStart;\n        let sendMessageEnd;\n        try {\n            sendMessageStart = Date.now();\n            await this._sendMessageToStream(stream, streamMessage);\n            sendMessageEnd = Date.now();\n        } catch (error) {\n            sendMessageEnd = Date.now();\n            nackMessage.data.errorMessage = `Unable to send message to peer: ${peerIdString}. protocol: ${protocol}, messageType: ${messageType}, operationId: ${operationId}, execution time: ${\n                sendMessageEnd - sendMessageStart\n            } ms. Error: ${error.message}`;\n\n            return nackMessage;\n        }\n\n        let readResponseStart;\n        let readResponseEnd;\n        let response;\n        const abortSignalEventListener = async () => {\n            stream.abort();\n            response = null;\n        };\n        const timeoutController = new TimeoutController(timeout);\n        try {\n            readResponseStart = Date.now();\n\n            timeoutController.signal.addEventListener('abort', abortSignalEventListener, {\n                once: true,\n            });\n\n            response = await this._readMessageFromStream(\n                stream,\n                this.isResponseValid.bind(this),\n                peerIdString,\n            );\n\n            if (timeoutController.signal.aborted) {\n                throw Error('Message timed out!');\n            }\n\n            timeoutController.signal.removeEventListener('abort', abortSignalEventListener);\n            timeoutController.clear();\n\n            readResponseEnd = Date.now();\n        } catch (error) {\n            timeoutController.signal.removeEventListener('abort', abortSignalEventListener);\n            timeoutController.clear();\n\n            readResponseEnd = Date.now();\n            nackMessage.data.errorMessage = `Unable to read response from peer ${peerIdString}. protocol: ${protocol}, messageType: ${messageType} , operationId: ${operationId}, execution time: ${\n                readResponseEnd - readResponseStart\n            } ms. Error: ${error.message}`;\n\n            return nackMessage;\n        }\n\n        this.logger.trace(\n            `Receiving response from ${peerIdString}. protocol: ${protocol}, messageType: ${\n                response.message?.header?.messageType\n            }, operationId: ${operationId}, execution time: ${\n                readResponseEnd - readResponseStart\n            } ms.`,\n        );\n\n        if (!response.valid) {\n            nackMessage.data.errorMessage = 'Invalid response';\n\n            return nackMessage;\n        }\n\n        return response.message;\n    }\n\n    async sendMessageResponse(protocol, peerIdString, messageType, operationId, message) {\n        this.logger.debug(\n            `Sending response from ${this.config.id} to ${peerIdString}: protocol: ${protocol}, messageType: ${messageType};`,\n        );\n        const stream = this.getSessionStream(operationId, peerIdString);\n\n        if (!stream) {\n            throw Error(`Unable to find opened stream for remotePeerId: ${peerIdString}`);\n        }\n\n        const response = this.createStreamMessage(message, operationId, messageType);\n\n        await this._sendMessageToStream(stream, response);\n    }\n\n    async _sendMessageToStream(stream, message) {\n        const stringifiedHeader = JSON.stringify(message.header);\n        const stringifiedData = JSON.stringify(message.data);\n\n        const chunks = [stringifiedHeader];\n        const chunkSize = BYTES_IN_MEGABYTE; // 1 MB\n\n        // split data into 1 MB chunks\n        for (let i = 0; i < stringifiedData.length; i += chunkSize) {\n            chunks.push(stringifiedData.slice(i, i + chunkSize));\n        }\n\n        await pipe(\n            chunks,\n            // turn strings into buffers\n            (source) => map(source, (string) => Buffer.from(string)),\n            // Encode with length prefix (so receiving side knows how much data is coming)\n            encode(),\n            // Write to the stream (the sink)\n            stream.sink,\n        );\n    }\n\n    async _readMessageFromStream(stream, isMessageValid, peerIdString) {\n        return pipe(\n            // Read from the stream (the source)\n            stream.source,\n            // Decode length-prefixed data\n            decode(),\n            // Turn buffers into strings\n            (source) => map(source, (buf) => buf.toString()),\n            // Sink function\n            (source) => this.readMessageSink(source, isMessageValid, peerIdString),\n        );\n    }\n\n    async readMessageSink(source, isMessageValid, peerIdString) {\n        const message = { header: { operationId: '' }, data: {} };\n        // we expect first buffer to be header\n        const stringifiedHeader = (await source.next()).value;\n\n        if (!stringifiedHeader?.length) {\n            return { message, valid: false, busy: false };\n        }\n\n        try {\n            message.header = JSON.parse(stringifiedHeader);\n        } catch (error) {\n            // Return the same format as invalid request case\n            return { message, valid: false, busy: false };\n        }\n\n        // validate request / response\n        if (!(await isMessageValid(message.header, peerIdString))) {\n            return { message, valid: false };\n        }\n\n        // business check if PROTOCOL_INIT message\n        if (\n            message.header.messageType === NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_INIT &&\n            this.isBusy()\n        ) {\n            return { message, valid: true, busy: true };\n        }\n\n        let stringifiedData = '';\n        // read data the data\n\n        try {\n            for await (const chunk of source) {\n                stringifiedData += chunk;\n            }\n            message.data = JSON.parse(stringifiedData);\n        } catch (error) {\n            // If data parsing fails, return invalid message response\n            return { message, valid: false, busy: false };\n        }\n\n        return { message, valid: true, busy: false };\n    }\n\n    async isRequestValid(header, peerIdString) {\n        // filter spam requests\n        if (await this.limitRequest(header, peerIdString)) return false;\n\n        // header well formed\n        if (\n            !header.operationId ||\n            !header.messageType ||\n            !Object.keys(NETWORK_MESSAGE_TYPES.REQUESTS).includes(header.messageType)\n        )\n            return false;\n        if (header.messageType === NETWORK_MESSAGE_TYPES.REQUESTS.PROTOCOL_INIT) {\n            return true;\n        }\n\n        return this.sessionExists(peerIdString, header.operationId);\n    }\n\n    sessionExists() {\n        return true;\n    }\n\n    async isResponseValid() {\n        return true;\n    }\n\n    healthCheck() {\n        // TODO: broadcast ping or sent msg to yourself\n        const connectedNodes = this.node.connectionManager.size;\n        if (connectedNodes > 0) return true;\n        return false;\n    }\n\n    async limitRequest(header, peerIdString) {\n        // if (header.sessionId && this.sessions.receiver[header.sessionId]) return false;\n\n        if (this.blackList[peerIdString]) {\n            const remainingMinutes = Math.floor(\n                NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES -\n                    (Date.now() - this.blackList[peerIdString]) / (1000 * 60),\n            );\n\n            if (remainingMinutes > 0) {\n                this.logger.debug(\n                    `Blocking request from ${peerIdString}. Node is blacklisted for ${remainingMinutes} minutes.`,\n                );\n\n                return true;\n            }\n            delete this.blackList[peerIdString];\n        }\n\n        if (await this.rateLimiter.spamDetection.limit(peerIdString)) {\n            this.blackList[peerIdString] = Date.now();\n            this.logger.debug(\n                `Blocking request from ${peerIdString}. Spammer detected and blacklisted for ${NETWORK_API_BLACK_LIST_TIME_WINDOW_MINUTES} minutes.`,\n            );\n\n            return true;\n        }\n        if (await this.rateLimiter.basicRateLimiter.limit(peerIdString)) {\n            this.logger.debug(\n                `Blocking request from ${peerIdString}. Max number of requests exceeded.`,\n            );\n\n            return true;\n        }\n\n        return false;\n    }\n\n    isBusy() {\n        const distinctOperations = new Set();\n        for (const peerId in this.sessions) {\n            for (const operationId in Object.keys(this.sessions[peerId])) {\n                distinctOperations.add(operationId);\n            }\n        }\n        return toobusy(); // || distinctOperations.size > constants.MAX_OPEN_SESSIONS;\n    }\n\n    getPrivateKey() {\n        return this.config.privateKey;\n    }\n\n    getName() {\n        return 'Libp2p';\n    }\n\n    async findPeer(peerId) {\n        return this.node.peerRouting.findPeer(createFromB58String(peerId));\n    }\n\n    async dial(peerId) {\n        return this.node.dial(createFromB58String(peerId));\n    }\n\n    async getPeerInfo(peerId) {\n        return this.node.peerStore.get(createFromB58String(peerId));\n    }\n\n    removeCachedSession(operationId, peerIdString) {\n        if (this.sessions[peerIdString]?.[operationId]?.stream) {\n            this.sessions[peerIdString][operationId].stream.close();\n            delete this.sessions[peerIdString][operationId];\n            this.logger.trace(\n                `Removed session for remotePeerId: ${peerIdString}, operationId: ${operationId}.`,\n            );\n        }\n    }\n}\n\nexport default Libp2pService;\n"
  },
  {
    "path": "src/modules/network/network-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass NetworkModuleManager extends BaseModuleManager {\n    getName() {\n        return 'network';\n    }\n\n    async start() {\n        if (this.initialized) {\n            return this.getImplementation().module.start();\n        }\n    }\n\n    async onPeerConnected(listener) {\n        if (this.initialized) {\n            return this.getImplementation().module.onPeerConnected(listener);\n        }\n    }\n\n    getMultiaddrs() {\n        if (this.initialized) {\n            return this.getImplementation().module.getMultiaddrs();\n        }\n    }\n\n    getPeers() {\n        if (this.initialized) {\n            return this.getImplementation().module.getPeers();\n        }\n    }\n\n    async sendMessage(protocol, remotePeerId, messageType, operationId, message, timeout) {\n        if (this.initialized) {\n            return this.getImplementation().module.sendMessage(\n                protocol,\n                remotePeerId,\n                messageType,\n                operationId,\n                message,\n                timeout,\n            );\n        }\n    }\n\n    async sendMessageResponse(protocol, remotePeerId, messageType, operationId, message) {\n        if (this.initialized) {\n            return this.getImplementation().module.sendMessageResponse(\n                protocol,\n                remotePeerId,\n                messageType,\n                operationId,\n                message,\n            );\n        }\n    }\n\n    handleMessage(protocol, handler, options) {\n        if (this.initialized) {\n            this.getImplementation().module.handleMessage(protocol, handler, options);\n        }\n    }\n\n    getPeerId() {\n        if (this.initialized) {\n            return this.getImplementation().module.getPeerId();\n        }\n    }\n\n    async healthCheck() {\n        if (this.initialized) {\n            return this.getImplementation().module.healthCheck();\n        }\n    }\n\n    async findPeer(peerId) {\n        if (this.initialized) {\n            return this.getImplementation().module.findPeer(peerId);\n        }\n    }\n\n    async dial(peerId) {\n        if (this.initialized) {\n            return this.getImplementation().module.dial(peerId);\n        }\n    }\n\n    async getPeerInfo(peerId) {\n        if (this.initialized) {\n            return this.getImplementation().module.getPeerInfo(peerId);\n        }\n    }\n\n    removeCachedSession(operationId, remotePeerId) {\n        if (this.initialized) {\n            this.getImplementation().module.removeCachedSession(operationId, remotePeerId);\n        }\n    }\n}\n\nexport default NetworkModuleManager;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20211117005500-create-commands.js",
    "content": "export const up = async ({ context: { queryInterface, Sequelize } }) => {\n    await queryInterface.createTable('commands', {\n        id: {\n            allowNull: false,\n            primaryKey: true,\n            type: Sequelize.STRING,\n        },\n        name: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        data: {\n            type: Sequelize.JSON,\n            allowNull: false,\n        },\n        sequence: {\n            type: Sequelize.JSON,\n            allowNull: true,\n        },\n        ready_at: {\n            type: Sequelize.INTEGER,\n            allowNull: false,\n        },\n        delay: {\n            type: Sequelize.INTEGER,\n            allowNull: false,\n        },\n        started_at: {\n            type: Sequelize.INTEGER,\n            allowNull: true,\n        },\n        deadline_at: {\n            type: Sequelize.INTEGER,\n            allowNull: true,\n        },\n        period: {\n            type: Sequelize.INTEGER,\n            allowNull: true,\n        },\n        status: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        message: {\n            type: Sequelize.TEXT('long'),\n            allowNull: true,\n        },\n        parent_id: {\n            type: Sequelize.STRING,\n            allowNull: true,\n        },\n        retries: {\n            type: Sequelize.INTEGER,\n            allowNull: true,\n        },\n        transactional: {\n            type: Sequelize.BOOLEAN,\n            allowNull: false,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n};\n\nexport const down = async ({ context: { queryInterface } }) => {\n    await queryInterface.dropTable('commands');\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20211117005504-create-operation_ids.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    return queryInterface.createTable('operation_ids', {\n        operation_id: {\n            allowNull: false,\n            primaryKey: true,\n            type: Sequelize.STRING,\n        },\n        data: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        timestamp: {\n            type: Sequelize.BIGINT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    return queryInterface.dropTable('operation_ids');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220620100000-create-publish.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('publish', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('publish');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220620100005-create-publish-response.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('publish_response', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        keyword: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        message: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('publish_response');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220623125000-create-get.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('get', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('get');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220623125001-create-get-response.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('get_response', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        keyword: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        message: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('get_response');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220624020509-create-event.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('event', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        name: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        timestamp: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        value1: {\n            allowNull: true,\n            type: Sequelize.STRING,\n        },\n        value2: {\n            allowNull: true,\n            type: Sequelize.STRING,\n        },\n        value3: {\n            allowNull: true,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('event');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220624103229-create-ability.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('ability', {\n        id: {\n            allowNull: false,\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        name: {\n            type: Sequelize.STRING,\n            unique: true,\n        },\n        created_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('ability');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220624103610-create-role.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('role', {\n        id: {\n            allowNull: false,\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        name: {\n            type: Sequelize.STRING,\n            unique: true,\n        },\n        created_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('role');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220624103615-create-user.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('user', {\n        id: {\n            allowNull: false,\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        name: {\n            type: Sequelize.STRING,\n            unique: true,\n        },\n        role_id: {\n            type: Sequelize.INTEGER,\n            references: {\n                model: 'role',\n                key: 'id',\n            },\n        },\n        created_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('user');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220624103658-create-token.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('token', {\n        id: {\n            allowNull: false,\n            primaryKey: true,\n            type: Sequelize.STRING,\n        },\n        name: {\n            allowNull: false,\n            type: Sequelize.STRING,\n            unique: true,\n        },\n        user_id: {\n            type: Sequelize.INTEGER,\n            references: {\n                model: 'user',\n                key: 'id',\n            },\n        },\n        expires_at: {\n            type: Sequelize.DATE,\n            allowNull: true,\n        },\n        revoked: {\n            type: Sequelize.BOOLEAN,\n            defaultValue: false,\n        },\n        created_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('token');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220624113659-create-role-ability.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('role_ability', {\n        id: {\n            allowNull: false,\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        ability_id: {\n            type: Sequelize.INTEGER,\n            references: {\n                model: 'ability',\n                key: 'id',\n            },\n        },\n        role_id: {\n            type: Sequelize.INTEGER,\n            references: {\n                model: 'role',\n                key: 'id',\n            },\n        },\n        created_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: true,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('role_ability');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20220628113824-add-predefined-auth-entities.js",
    "content": "const routes = [\n    'PUBLISH',\n    'PROVISION',\n    'UPDATE',\n    'GET',\n    'SEARCH',\n    'SEARCH_ASSERTION',\n    'QUERY',\n    'PROOFS',\n    'OPERATION_RESULT',\n    'INFO',\n];\n\nexport async function up({ context: { queryInterface } }) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await queryInterface.bulkInsert(\n            'ability',\n            routes.map((r) => ({ name: r })),\n            {\n                transaction,\n            },\n        );\n        const [abilities] = await queryInterface.sequelize.query('SELECT id from ability', {\n            transaction,\n        });\n\n        await queryInterface.bulkInsert(\n            'role',\n            [\n                {\n                    name: 'ADMIN',\n                },\n            ],\n            {\n                transaction,\n            },\n        );\n\n        const [[role]] = await queryInterface.sequelize.query(\n            \"SELECT id from role where name='ADMIN'\",\n            {\n                transaction,\n            },\n        );\n\n        const roleAbilities = abilities.map((e) => ({\n            ability_id: e.id,\n            role_id: role.id,\n        }));\n\n        await queryInterface.bulkInsert('role_ability', roleAbilities, { transaction });\n\n        await queryInterface.bulkInsert(\n            'user',\n            [\n                {\n                    name: 'node-runner',\n                    role_id: role.id,\n                },\n            ],\n            { transaction },\n        );\n\n        transaction.commit();\n    } catch (e) {\n        transaction.rollback();\n        throw e;\n    }\n}\nexport async function down({ context: { queryInterface } }) {\n    queryInterface.sequelize.query('TRUNCATE TABLE role_ability;');\n    queryInterface.sequelize.query('TRUNCATE TABLE role;');\n    queryInterface.sequelize.query('TRUNCATE TABLE ability;');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221025120253-create-blockchain-event.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('blockchain_event', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        contract: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        blockchain_id: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        event: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        data: {\n            allowNull: false,\n            type: Sequelize.TEXT('long'),\n        },\n        block: {\n            allowNull: false,\n            type: Sequelize.INTEGER,\n        },\n        processed: {\n            allowNull: false,\n            type: Sequelize.BOOLEAN,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('blockchain_event');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221025212800-create-shard.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('shard', {\n        peer_id: {\n            type: Sequelize.STRING,\n            primaryKey: true,\n        },\n        blockchain_id: {\n            type: Sequelize.STRING,\n            primaryKey: true,\n        },\n        ask: {\n            type: Sequelize.INTEGER,\n            allowNull: false,\n        },\n        stake: {\n            type: Sequelize.INTEGER,\n            allowNull: false,\n        },\n        last_seen: {\n            type: Sequelize.DATE,\n            allowNull: false,\n            defaultValue: new Date(0),\n        },\n        last_dialed: {\n            type: Sequelize.DATE,\n            allowNull: false,\n            defaultValue: new Date(0),\n        },\n        sha256: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('shard');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221028125900-create-blockchain.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('blockchain', {\n        blockchain_id: {\n            type: Sequelize.STRING,\n            primaryKey: true,\n        },\n        contract: {\n            type: Sequelize.STRING,\n            primaryKey: true,\n        },\n        last_checked_block: {\n            type: Sequelize.BIGINT,\n            allowNull: false,\n            defaultValue: -1,\n        },\n        last_checked_timestamp: {\n            type: Sequelize.BIGINT,\n            allowNull: false,\n            defaultValue: 0,\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('blockchain');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221114115524-update-publish-add-agreement-data.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('publish', 'agreementId', {\n        type: Sequelize.STRING,\n    });\n    await queryInterface.addColumn('publish', 'agreementStatus', {\n        type: Sequelize.STRING,\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('publish', 'agreementId');\n    await queryInterface.removeColumn('publish', 'agreementStatus');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221206183634-update-shard-types.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('shard', 'ask', {\n        type: Sequelize.STRING,\n    });\n    await queryInterface.changeColumn('shard', 'stake', {\n        type: Sequelize.STRING,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('shard', 'ask', {\n        type: Sequelize.INTEGER,\n    });\n    await queryInterface.changeColumn('shard', 'ask', {\n        type: Sequelize.INTEGER,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221214110050-update-commands-types.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('commands', 'ready_at', {\n        type: Sequelize.BIGINT,\n    });\n    await queryInterface.changeColumn('commands', 'delay', {\n        type: Sequelize.BIGINT,\n    });\n    await queryInterface.changeColumn('commands', 'started_at', {\n        type: Sequelize.BIGINT,\n    });\n    await queryInterface.changeColumn('commands', 'deadline_at', {\n        type: Sequelize.BIGINT,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('commands', 'ready_at', {\n        type: Sequelize.INTEGER,\n    });\n    await queryInterface.changeColumn('commands', 'delay', {\n        type: Sequelize.INTEGER,\n    });\n    await queryInterface.changeColumn('commands', 'started_at', {\n        type: Sequelize.INTEGER,\n    });\n    await queryInterface.changeColumn('commands', 'deadline_at', {\n        type: Sequelize.INTEGER,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20221215130500-update-event-types.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('event', 'value1', {\n        type: Sequelize.TEXT,\n    });\n    await queryInterface.changeColumn('event', 'value2', {\n        type: Sequelize.TEXT,\n    });\n    await queryInterface.changeColumn('event', 'value3', {\n        type: Sequelize.TEXT,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('event', 'value1', {\n        type: Sequelize.STRING,\n    });\n    await queryInterface.changeColumn('event', 'value2', {\n        type: Sequelize.STRING,\n    });\n    await queryInterface.changeColumn('event', 'value3', {\n        type: Sequelize.STRING,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230216112400-add-abilities.js",
    "content": "const newRoutes = ['BID-SUGGESTION', 'LOCAL-STORE'];\n\nasync function getRoleAbilities(names, queryInterface, transaction) {\n    const [abilities] = await queryInterface.sequelize.query(\n        `SELECT id from ability where name IN (${names.map((name) => `'${name}'`).join(', ')})`,\n        {\n            transaction,\n        },\n    );\n\n    const [[role]] = await queryInterface.sequelize.query(\n        \"SELECT id from role where name='ADMIN'\",\n        {\n            transaction,\n        },\n    );\n\n    return abilities.map((ability) => ({\n        ability_id: ability.id,\n        role_id: role.id,\n    }));\n}\n\nasync function removeAbilities(names, queryInterface, transaction) {\n    await queryInterface.bulkDelete(\n        'role_ability',\n        await getRoleAbilities(names, queryInterface, transaction),\n        { transaction },\n    );\n    await queryInterface.bulkDelete(\n        'ability',\n        names.map((name) => ({ name })),\n        { transaction },\n    );\n}\n\nasync function addAbilities(names, queryInterface, transaction) {\n    await queryInterface.bulkInsert(\n        'ability',\n        names.map((name) => ({ name })),\n        { transaction },\n    );\n    await queryInterface.bulkInsert(\n        'role_ability',\n        await getRoleAbilities(names, queryInterface, transaction),\n        { transaction },\n    );\n}\n\nexport async function up({ context: { queryInterface } }) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await addAbilities(newRoutes, queryInterface, transaction);\n        transaction.commit();\n    } catch (e) {\n        transaction.rollback();\n        throw e;\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await removeAbilities(newRoutes, queryInterface, transaction);\n        transaction.commit();\n    } catch (e) {\n        transaction.rollback();\n        throw e;\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230227094500-create-update.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('update', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('update');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230303131200-update-publish-remove-agreement-data.js",
    "content": "const columns = ['agreementId', 'agreementStatus'];\n\nexport async function up({ context: { queryInterface } }, logger) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await Promise.all(\n            columns.map((column) =>\n                queryInterface.removeColumn('publish', column, { transaction }).catch((error) => {\n                    logger.warn(`Error removing column: ${column}: ${error.message}`);\n                }),\n            ),\n        );\n\n        await transaction.commit();\n    } catch (error) {\n        await transaction.rollback();\n        throw error;\n    }\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await Promise.all(\n            columns.map((column) =>\n                queryInterface.addColumn(\n                    'publish',\n                    column,\n                    {\n                        type: Sequelize.STRING,\n                    },\n                    { transaction },\n                ),\n            ),\n        );\n\n        await transaction.commit();\n    } catch (error) {\n        await transaction.rollback();\n        throw error;\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230303131400-create-update-response.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('update_response', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        keyword: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        message: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('update_response');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230413194400-update-command-period-type.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('commands', 'period', {\n        type: Sequelize.BIGINT,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('commands', 'period', {\n        type: Sequelize.BIGINT,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230419140000-create-service-agreements.js",
    "content": "export const up = async ({ context: { queryInterface, Sequelize } }) => {\n    await queryInterface.createTable('service_agreement', {\n        blockchain_id: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        asset_storage_contract_address: {\n            type: Sequelize.STRING(42),\n            allowNull: false,\n        },\n        token_id: {\n            type: Sequelize.INTEGER.UNSIGNED,\n            allowNull: false,\n        },\n        agreement_id: {\n            type: Sequelize.STRING,\n            primaryKey: true,\n        },\n        start_time: {\n            type: Sequelize.INTEGER.UNSIGNED,\n            allowNull: false,\n        },\n        epochs_number: {\n            type: Sequelize.SMALLINT.UNSIGNED,\n            allowNull: false,\n        },\n        epoch_length: {\n            type: Sequelize.INTEGER.UNSIGNED,\n            allowNull: false,\n        },\n        score_function_id: {\n            type: Sequelize.TINYINT.UNSIGNED,\n            allowNull: false,\n        },\n        state_index: {\n            type: Sequelize.SMALLINT.UNSIGNED,\n            allowNull: false,\n        },\n        assertion_id: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        hash_function_id: {\n            type: Sequelize.TINYINT.UNSIGNED,\n            allowNull: false,\n        },\n        keyword: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        proof_window_offset_perc: {\n            type: Sequelize.TINYINT.UNSIGNED,\n            allowNull: false,\n        },\n        last_commit_epoch: {\n            type: Sequelize.SMALLINT.UNSIGNED,\n        },\n        last_proof_epoch: {\n            type: Sequelize.SMALLINT.UNSIGNED,\n        },\n    });\n};\n\nexport const down = async ({ context: { queryInterface } }) => {\n    await queryInterface.dropTable('service_agreement');\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20230502110300-add-blockchain-event-index.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    await queryInterface.addIndex('blockchain_event', ['processed'], {\n        name: 'idx_blockchain_event_processed',\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeIndex('blockchain_event', 'idx_blockchain_event_processed');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20231201140100-event-add-blockchain-id.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('event', 'blockchain_id', {\n        type: Sequelize.STRING,\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('event', 'blockchain_id');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20231221131300-update-abilities.js",
    "content": "const newRoutes = [\n    'V0/PUBLISH',\n    'V0/UPDATE',\n    'V0/GET',\n    'V0/QUERY',\n    'V0/OPERATION_RESULT',\n    'V0/INFO',\n    'V0/BID-SUGGESTION',\n    'V0/LOCAL-STORE',\n];\nconst outdatedRoutes = ['PROVISION', 'SEARCH', 'SEARCH_ASSERTION', 'PROOFS'];\n\nasync function getAbilityIds(names, queryInterface, transaction) {\n    const [abilities] = await queryInterface.sequelize.query(\n        `SELECT id FROM ability WHERE name IN (${names.map((name) => `'${name}'`).join(', ')})`,\n        { transaction },\n    );\n    return abilities.map((ability) => ability.id);\n}\n\nasync function getRoleIds(queryInterface, transaction) {\n    const [roles] = await queryInterface.sequelize.query(\n        'SELECT id FROM role WHERE name IS NOT NULL;',\n        {\n            transaction,\n        },\n    );\n\n    return roles.map((role) => role.id);\n}\n\nasync function getRoleAbilities(names, queryInterface, transaction) {\n    const abilityIds = await getAbilityIds(names, queryInterface, transaction);\n    const roleIds = await getRoleIds(queryInterface, transaction);\n\n    return roleIds.flatMap((roleId) =>\n        abilityIds.map((abilityId) => ({\n            ability_id: abilityId,\n            role_id: roleId,\n        })),\n    );\n}\n\nasync function removeAbilities(names, queryInterface, transaction) {\n    const roleIds = await getRoleIds(queryInterface, transaction);\n    const abilityIds = await getAbilityIds(names, queryInterface, transaction);\n\n    await queryInterface.bulkDelete(\n        'role_ability',\n        {\n            role_id: roleIds,\n            ability_id: abilityIds,\n        },\n        { transaction },\n    );\n\n    await queryInterface.bulkDelete('ability', { id: abilityIds }, { transaction });\n}\n\nasync function addAbilities(names, queryInterface, transaction) {\n    await queryInterface.bulkInsert(\n        'ability',\n        names.map((name) => ({ name })),\n        { transaction },\n    );\n    await queryInterface.bulkInsert(\n        'role_ability',\n        await getRoleAbilities(names, queryInterface, transaction),\n        { transaction },\n    );\n}\n\nexport async function up({ context: { queryInterface } }) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await addAbilities(newRoutes, queryInterface, transaction);\n        await removeAbilities(outdatedRoutes, queryInterface, transaction);\n        transaction.commit();\n    } catch (e) {\n        transaction.rollback();\n        throw e;\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    const transaction = await queryInterface.sequelize.transaction();\n    try {\n        await removeAbilities(newRoutes, queryInterface, transaction);\n        await addAbilities(outdatedRoutes, queryInterface, transaction);\n        transaction.commit();\n    } catch (e) {\n        transaction.rollback();\n        throw e;\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20233010122500-update-blockchain-id.js",
    "content": "const CHAIN_IDS = {\n    development: 31337,\n    test: 31337,\n    devnet: 2160,\n    testnet: 20430,\n    mainnet: 2043,\n};\nconst chainId = CHAIN_IDS[process.env.NODE_ENV];\n\nexport async function up({ context: { queryInterface } }) {\n    await queryInterface.sequelize.query(`\n        update shard set blockchain_id='otp:${chainId}'\n    `);\n\n    await queryInterface.sequelize.query(`\n        update service_agreement set blockchain_id='otp:${chainId}'\n    `);\n\n    await queryInterface.sequelize.query(`\n        update blockchain_event set blockchain_id='otp:${chainId}'\n    `);\n\n    await queryInterface.sequelize.query(`\n        update blockchain set blockchain_id='otp:${chainId}'\n    `);\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.sequelize.query(`\n        update shard set blockchain_id='otp'\n    `);\n\n    await queryInterface.sequelize.query(`\n        update service_agreement set blockchain_id='otp'\n    `);\n\n    await queryInterface.sequelize.query(`\n        update blockchain_event set blockchain_id='otp'\n    `);\n\n    await queryInterface.sequelize.query(`\n        update blockchain set blockchain_id='otp'\n    `);\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20233011121700-remove-blockchain-info.js",
    "content": "const chiadoBlockchainId = 'gnosis:10200';\n\nexport async function up({ context: { queryInterface } }) {\n    await queryInterface.sequelize.query(`\n        delete from blockchain where blockchain_id='${chiadoBlockchainId}'\n    `);\n}\n\n// eslint-disable-next-line no-empty-function\nexport async function down() {}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240126120000-shard-add-sha256blobl.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    const tableInfo = await queryInterface.describeTable('shard');\n\n    if (!tableInfo.sha256_blob) {\n        await queryInterface.addColumn('shard', 'sha256_blob', {\n            type: Sequelize.BLOB,\n        });\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('shard', 'sha256_blob');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240201100000-remove-sha256Blob.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    const tableInfo = await queryInterface.describeTable('shard');\n\n    if (tableInfo.sha256_blob) {\n        await queryInterface.removeColumn('shard', 'sha256_blob');\n    }\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    const tableInfo = await queryInterface.describeTable('shard');\n\n    if (!tableInfo.sha256_blob) {\n        await queryInterface.addColumn('shard', 'sha256_blob', {\n            type: Sequelize.BLOB,\n        });\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240221162000-add-service-agreement-data-source.js",
    "content": "import { SERVICE_AGREEMENT_SOURCES } from '../../../../../constants/constants.js';\n\nexport async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('service_agreement', 'data_source', {\n        type: Sequelize.ENUM(...Object.values(SERVICE_AGREEMENT_SOURCES)),\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('service_agreement', 'data_source');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240429083058-create-paranet.js",
    "content": "export const up = async ({ context: { queryInterface, Sequelize } }) => {\n    await queryInterface.createTable('paranet', {\n        id: {\n            allowNull: false,\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        name: {\n            type: Sequelize.STRING,\n        },\n        blockchain_id: {\n            type: Sequelize.STRING,\n            primaryKey: true,\n        },\n        description: {\n            type: Sequelize.STRING,\n        },\n        paranet_id: {\n            type: Sequelize.STRING,\n        },\n        ka_count: {\n            type: Sequelize.INTEGER,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n};\n\nexport const down = async ({ context: { queryInterface } }) => {\n    await queryInterface.dropTable('Paranet');\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240529070000-create-missed-paranet-asset.js",
    "content": "export const up = async ({ context: { queryInterface, Sequelize } }) => {\n    await queryInterface.createTable('missed_paranet_asset', {\n        id: {\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        blockchain_id: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        ual: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        paranet_ual: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        knowledge_asset_id: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n};\n\nexport const down = async ({ context: { queryInterface } }) => {\n    await queryInterface.dropTable('missed_paranet_asset');\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240923195000-create-publish-paranet.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('publish_paranet', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('publish_paranet');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240924161700-create-paranet-synced-asset.js",
    "content": "export const up = async ({ context: { queryInterface, Sequelize } }) => {\n    await queryInterface.createTable('paranet_synced_asset', {\n        id: {\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        blockchain_id: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        ual: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        paranet_ual: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        public_assertion_id: {\n            allowNull: true,\n            type: Sequelize.STRING,\n        },\n        private_assertion_id: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        sender: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        transaction_hash: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n\n    const [triggerInsertExists] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS trigger_exists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE()\n          AND trigger_name = 'before_insert_paranet_synced_asset';\n    `);\n    if (triggerInsertExists[0].trigger_exists === 0) {\n        await queryInterface.sequelize.query(`\n            CREATE TRIGGER before_insert_paranet_synced_asset\n            BEFORE INSERT ON paranet_synced_asset\n            FOR EACH ROW\n            BEGIN\n                SET NEW.created_at = NOW();\n            END;\n        `);\n    }\n\n    const [triggerUpdateExists] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS trigger_exists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE()\n          AND trigger_name = 'before_update_paranet_synced_asset';\n    `);\n    if (triggerUpdateExists[0].trigger_exists === 0) {\n        await queryInterface.sequelize.query(`\n            CREATE TRIGGER before_update_paranet_synced_asset\n            BEFORE UPDATE ON paranet_synced_asset\n            FOR EACH ROW\n            BEGIN\n                SET NEW.updated_at = NOW();\n            END;\n        `);\n    }\n\n    const indexes = [\n        { name: 'idx_paranet_ual_created_at', columns: '(paranet_ual, created_at)' },\n        { name: 'idx_sender', columns: '(sender)' },\n        { name: 'idx_paranet_ual_unique', columns: '(paranet_ual)' },\n    ];\n\n    for (const index of indexes) {\n        // eslint-disable-next-line no-await-in-loop\n        const [indexExists] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS index_exists\n            FROM information_schema.statistics\n            WHERE table_schema = DATABASE()\n              AND table_name = 'paranet_synced_asset'\n              AND index_name = '${index.name}';\n        `);\n        if (indexExists[0].index_exists === 0) {\n            // eslint-disable-next-line no-await-in-loop\n            await queryInterface.sequelize.query(`\n                CREATE INDEX ${index.name}\n                ON paranet_synced_asset ${index.columns};\n            `);\n        }\n    }\n};\n\nexport const down = async ({ context: { queryInterface } }) => {\n    await queryInterface.dropTable('paranet_synced_asset');\n\n    await queryInterface.sequelize.query(`\n        DROP TRIGGER IF EXISTS before_insert_paranet_synced_asset;\n    `);\n\n    await queryInterface.sequelize.query(`\n        DROP TRIGGER IF EXISTS before_update_paranet_synced_asset;\n    `);\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240924205500-create-publish-paranet-response.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('publish_paranet_response', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        keyword: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        message: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('publish_paranet_response');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240927110000-change-paranet-synced-asset-nullable-assertions.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('paranet_synced_asset', 'public_assertion_id', {\n        type: Sequelize.STRING,\n        allowNull: false,\n    });\n\n    await queryInterface.changeColumn('paranet_synced_asset', 'private_assertion_id', {\n        type: Sequelize.STRING,\n        allowNull: true,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('paranet_synced_asset', 'public_assertion_id', {\n        type: Sequelize.STRING,\n        allowNull: true,\n    });\n\n    await queryInterface.changeColumn('paranet_synced_asset', 'private_assertion_id', {\n        type: Sequelize.STRING,\n        allowNull: false,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20240930113000-add-error-message.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('missed_paranet_asset', 'error_message', {\n        type: Sequelize.TEXT,\n        allowNull: true,\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('missed_paranet_asset', 'error_message');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241011112100-remove-knowledge-asset-id.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('missed_paranet_asset', 'knowledge_asset_id');\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('missed_paranet_asset', 'knowledge_asset_id', {\n        type: Sequelize.STRING,\n        allowNull: false,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241014164500-paranet-synced-asset-optional-fileds.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('paranet_synced_asset', 'sender', {\n        type: Sequelize.STRING,\n        allowNull: true,\n    });\n    await queryInterface.changeColumn('paranet_synced_asset', 'transaction_hash', {\n        type: Sequelize.STRING,\n        allowNull: true,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('paranet_synced_asset', 'sender', {\n        type: Sequelize.STRING,\n        allowNull: false,\n    });\n    await queryInterface.changeColumn('paranet_synced_asset', 'transaction_hash', {\n        type: Sequelize.STRING,\n        allowNull: false,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241023170300-add-synced-data-source.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('paranet_synced_asset', 'data_source', {\n        type: Sequelize.TEXT,\n        allowNull: true,\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('paranet_synced_asset', 'data_source');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241105150000-change-data-source-col-type-in-paranet-synced-asset.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('paranet_synced_asset', 'data_source', {\n        type: Sequelize.ENUM('sync', 'local_store'),\n        allowNull: true,\n    });\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.changeColumn('paranet_synced_asset', 'data_source', {\n        type: Sequelize.TEXT,\n        allowNull: true,\n    });\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241105160000-add-indexes-to-tables.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    const indexes = [\n        { table: 'shard', column: ['blockchain_id'], name: 'shard_blockchain_id_index' },\n        { table: 'shard', column: ['last_dialed'], name: 'last_dialed_index' },\n        { table: 'paranet_synced_asset', column: ['ual'], name: 'paranet_synced_asset_ual_index' },\n        {\n            table: 'paranet_synced_asset',\n            column: ['paranet_ual', 'data_source'],\n            name: 'paranet_ual_data_source_index',\n        },\n        {\n            table: 'paranet',\n            column: ['blockchain_id', 'paranet_id'],\n            name: 'blockchain_id_paranet_id_index',\n        },\n        { table: 'missed_paranet_asset', column: ['paranet_ual'], name: 'paranet_ual_index' },\n        { table: 'missed_paranet_asset', column: ['ual'], name: 'missed_paranet_asset_ual_index' },\n        { table: 'event', column: ['name', 'timestamp'], name: 'name_timestamp_index' },\n        { table: 'event', column: ['operation_id'], name: 'event_operation_id_index' },\n        { table: 'commands', column: ['name', 'status'], name: 'name_status_index' },\n        { table: 'commands', column: ['status', 'started_at'], name: 'status_started_at_index' },\n        { table: 'get', column: ['operation_id'], name: 'get_operation_id_index' },\n        { table: 'publish', column: ['operation_id'], name: 'publish_operation_id_index' },\n        { table: 'update', column: ['operation_id'], name: 'update_operation_id_index' },\n        {\n            table: 'publish_paranet',\n            column: ['operation_id'],\n            name: 'publish_paranet_operation_id_index',\n        },\n        { table: 'get', column: ['created_at'], name: 'get_created_at_index' },\n        { table: 'publish', column: ['created_at'], name: 'publish_created_at_index' },\n        { table: 'update', column: ['created_at'], name: 'update_created_at_index' },\n        {\n            table: 'publish_paranet',\n            column: ['created_at'],\n            name: 'publish_paranet_created_at_index',\n        },\n        {\n            table: 'get_response',\n            column: ['operation_id'],\n            name: 'get_response_operation_id_index',\n        },\n        { table: 'publish_response', column: ['operation_id'], name: 'operation_id_index' },\n        {\n            table: 'update_response',\n            column: ['operation_id'],\n            name: 'update_response_operation_id_index',\n        },\n        {\n            table: 'publish_paranet_response',\n            column: ['operation_id'],\n            name: 'publish_paranet_response_operation_id_index',\n        },\n        { table: 'get_response', column: ['created_at'], name: 'get_response_created_at_index' },\n        {\n            table: 'publish_response',\n            column: ['created_at'],\n            name: 'publish_response_created_at_index',\n        },\n        {\n            table: 'update_response',\n            column: ['created_at'],\n            name: 'update_response_created_at_index',\n        },\n        {\n            table: 'publish_paranet_response',\n            column: ['created_at'],\n            name: 'publish_paranet_response_created_at_index',\n        },\n        { table: 'blockchain', column: ['contract'], name: 'contract_index' },\n    ];\n\n    for (const index of indexes) {\n        const { table, column, name } = index;\n\n        // eslint-disable-next-line no-await-in-loop\n        const [indexExists] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS index_exists\n            FROM information_schema.statistics\n            WHERE table_schema = DATABASE()\n            AND table_name = '${table}'\n            AND index_name = '${name}';\n        `);\n        if (indexExists[0].index_exists === 0) {\n            // eslint-disable-next-line no-await-in-loop\n            await queryInterface.sequelize.query(`\n                CREATE INDEX \\`${name}\\`\n                ON \\`${table}\\` (${column.map((col) => `\\`${col}\\``).join(', ')});\n            `);\n        }\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    const indexes = [\n        { table: 'shard', name: 'shard_blockchain_id_index' },\n        { table: 'shard', name: 'last_dialed_index' },\n        { table: 'paranet_synced_asset', name: 'paranet_synced_asset_ual_index' },\n        { table: 'paranet_synced_asset', name: 'paranet_ual_data_source_index' },\n        { table: 'paranet', name: 'blockchain_id_paranet_id_index' },\n        { table: 'missed_paranet_asset', name: 'paranet_ual_index' },\n        { table: 'missed_paranet_asset', name: 'missed_paranet_asset_ual_index' },\n        { table: 'event', name: 'name_timestamp_index' },\n        { table: 'event', name: 'event_operation_id_index' },\n        { table: 'commands', name: 'name_status_index' },\n        { table: 'commands', name: 'status_started_at_index' },\n        { table: 'get', name: 'get_operation_id_index' },\n        { table: 'publish', name: 'publish_operation_id_index' },\n        { table: 'update', name: 'update_operation_id_index' },\n        { table: 'publish_paranet', name: 'publish_paranet_operation_id_index' },\n        { table: 'get', name: 'get_created_at_index' },\n        { table: 'publish', name: 'publish_created_at_index' },\n        { table: 'update', name: 'update_created_at_index' },\n        { table: 'publish_paranet', name: 'publish_paranet_created_at_index' },\n        { table: 'get_response', name: 'get_response_operation_id_index' },\n        { table: 'publish_response', name: 'publish_response_operation_id_index' },\n        { table: 'update_response', name: 'update_response_operation_id_index' },\n        {\n            table: 'publish_paranet_response',\n            name: 'publish_paranet_response_operation_id_index',\n        },\n        { table: 'get_response', name: 'get_response_created_at_index' },\n        { table: 'publish_response', name: 'publish_response_created_at_index' },\n        { table: 'update_response', name: 'update_response_created_at_index' },\n        {\n            table: 'publish_paranet_response',\n            name: 'publish_paranet_response_created_at_index',\n        },\n        { table: 'blockchain', name: 'contract_index' },\n    ];\n\n    for (const { table, name } of indexes) {\n        // eslint-disable-next-line no-await-in-loop\n        const [indexExists] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS index_exists\n            FROM information_schema.statistics\n            WHERE table_schema = DATABASE()\n            AND table_name = '${table}'\n            AND index_name = '${name}';\n        `);\n\n        if (indexExists[0].index_exists > 0) {\n            // eslint-disable-next-line no-await-in-loop\n            await queryInterface.removeIndex(table, name);\n        }\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241125151200-rename-keyword-column-to-datasetroot-in-responses.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    // Helper function to check if a column exists\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('publish_response', 'keyword')) {\n        await queryInterface.renameColumn('publish_response', 'keyword', 'dataset_root');\n    }\n\n    if (await columnExists('get_response', 'keyword')) {\n        await queryInterface.renameColumn('get_response', 'keyword', 'dataset_root');\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('publish_response', 'dataset_root')) {\n        await queryInterface.renameColumn('publish_response', 'dataset_root', 'keyword');\n    }\n\n    if (await columnExists('get_response', 'dataset_root')) {\n        await queryInterface.renameColumn('get_response', 'dataset_root', 'keyword');\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241126114400-add-commands-priority.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (!(await columnExists('commands', 'priority'))) {\n        await queryInterface.addColumn('commands', 'priority', {\n            type: Sequelize.BIGINT,\n        });\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('commands', 'priority')) {\n        await queryInterface.removeColumn('commands', 'priority');\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241129120000-add-commands-is_blocking.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (!(await columnExists('commands', 'is_blocking'))) {\n        await queryInterface.addColumn('commands', 'is_blocking', {\n            type: Sequelize.BOOLEAN,\n        });\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('commands', 'is_blocking')) {\n        await queryInterface.removeColumn('commands', 'is_blocking');\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241129125800-remove-datasetroot-response-table.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('publish_response', 'dataset_root')) {\n        await queryInterface.removeColumn('publish_response', 'dataset_root');\n    }\n\n    if (await columnExists('get_response', 'dataset_root')) {\n        await queryInterface.removeColumn('get_response', 'dataset_root');\n    }\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (!(await columnExists('publish_response', 'dataset_root'))) {\n        await queryInterface.addColumn('publish_response', 'dataset_root', {\n            type: Sequelize.STRING,\n            allowNull: false,\n        });\n    }\n\n    if (!(await columnExists('get_response', 'dataset_root'))) {\n        await queryInterface.addColumn('get_response', 'dataset_root', {\n            type: Sequelize.STRING,\n            allowNull: false,\n        });\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241201152000-update-blockchain-events.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    // Helper function to check if a column exists\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('blockchain_event', 'blockchain_id')) {\n        await queryInterface.renameColumn('blockchain_event', 'blockchain_id', 'blockchain');\n    }\n\n    if (await columnExists('blockchain_event', 'block')) {\n        await queryInterface.changeColumn('blockchain_event', 'block', {\n            type: Sequelize.BIGINT,\n        });\n\n        await queryInterface.renameColumn('blockchain_event', 'block', 'block_number');\n    }\n\n    if (!(await columnExists('blockchain_event', 'transaction_index'))) {\n        await queryInterface.addColumn('blockchain_event', 'transaction_index', {\n            type: Sequelize.BIGINT,\n        });\n    }\n\n    if (!(await columnExists('blockchain_event', 'log_index'))) {\n        await queryInterface.addColumn('blockchain_event', 'log_index', {\n            type: Sequelize.BIGINT,\n        });\n    }\n\n    if (!(await columnExists('blockchain_event', 'contract_address'))) {\n        await queryInterface.addColumn('blockchain_event', 'contract_address', {\n            type: Sequelize.STRING,\n        });\n    }\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('blockchain_event', 'block_number')) {\n        await queryInterface.renameColumn('blockchain_event', 'block_number', 'block');\n    }\n\n    if (await columnExists('blockchain_event', 'block')) {\n        await queryInterface.changeColumn('blockchain_event', 'block', {\n            type: Sequelize.INTEGER,\n        });\n    }\n\n    if (await columnExists('blockchain_event', 'blockchain')) {\n        await queryInterface.renameColumn('blockchain_event', 'blockchain', 'blockchain_id');\n    }\n\n    if (await columnExists('blockchain_event', 'transaction_index')) {\n        await queryInterface.removeColumn('blockchain_event', 'transaction_index');\n    }\n\n    if (await columnExists('blockchain_event', 'log_index')) {\n        await queryInterface.removeColumn('blockchain_event', 'log_index');\n    }\n\n    if (await columnExists('blockchain_event', 'contract_address')) {\n        await queryInterface.removeColumn('blockchain_event', 'contract_address');\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241202214500-update-blockchain-table.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    const tableInfo = await queryInterface.describeTable('blockchain');\n\n    if (tableInfo.blockchain_id) {\n        await queryInterface.renameColumn('blockchain', 'blockchain_id', 'blockchain');\n    }\n\n    await queryInterface.sequelize.query(`\n        DELETE t1\n        FROM blockchain t1\n        JOIN blockchain t2\n        ON t1.blockchain = t2.blockchain\n        AND (\n            t1.last_checked_block > t2.last_checked_block OR\n            (t1.last_checked_block = t2.last_checked_block AND t1.last_checked_timestamp > t2.last_checked_timestamp)\n        );\n    `);\n\n    await queryInterface.sequelize.query(`\n        ALTER TABLE blockchain DROP PRIMARY KEY, ADD PRIMARY KEY (blockchain);\n    `);\n\n    await queryInterface.removeColumn('blockchain', 'contract');\n}\n\nexport async function down({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.renameColumn('blockchain', 'blockchain', 'blockchain_id');\n\n    await queryInterface.addColumn('blockchain', 'contract', {\n        type: Sequelize.STRING,\n        allowNull: false,\n    });\n\n    await queryInterface.sequelize.query(`\n        ALTER TABLE blockchain DROP PRIMARY KEY, ADD PRIMARY KEY (blockchain_id, contract);\n    `);\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241203125000-create-finality.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('finality', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('finality');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241203125001-create-finality-response.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('finality_response', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        message: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('finality_response');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241211204400-rename-ask.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    async function tableExists(table) {\n        const [results] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS table_exists\n            FROM information_schema.tables\n            WHERE table_schema = DATABASE()\n              AND table_name = '${table}';\n        `);\n        return results[0].table_exists > 0;\n    }\n\n    if (await tableExists('finality')) {\n        await queryInterface.renameTable('finality', 'ask');\n    }\n\n    if (await tableExists('finality_response')) {\n        await queryInterface.renameTable('finality_response', 'ask_response');\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    async function tableExists(table) {\n        const [results] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS table_exists\n            FROM information_schema.tables\n            WHERE table_schema = DATABASE()\n              AND table_name = '${table}';\n        `);\n        return results[0].table_exists > 0;\n    }\n\n    if (await tableExists('ask')) {\n        await queryInterface.renameTable('ask', 'finality');\n    }\n\n    if (await tableExists('ask_response')) {\n        await queryInterface.renameTable('ask_response', 'finality_response');\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241211205400-create-finality-response.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('finality_response', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        message: {\n            allowNull: true,\n            type: Sequelize.TEXT,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('finality_response');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241211205400-create-finality-status.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('finality_status', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        ual: {\n            type: Sequelize.STRING,\n        },\n        peer_id: {\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n    await queryInterface.addConstraint('finality_status', {\n        fields: ['ual', 'peer_id'],\n        type: 'unique',\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('finality_status');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241211205400-create-finality.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('finality', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('finality');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241212122200-add-min-acks-reached-column.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.addColumn('operation_ids', 'min_acks_reached', {\n        type: Sequelize.BOOLEAN,\n    });\n}\n\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.removeColumn('operation_ids', 'min_acks_reached');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241215122200-create-paranet-kc.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('paranet_kc', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        blockchain_id: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        ual: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        paranet_ual: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        error_message: {\n            type: Sequelize.TEXT,\n            allowNull: true,\n        },\n        is_synced: {\n            type: Sequelize.BOOLEAN,\n            allowNull: false,\n            defaultValue: false,\n        },\n        retries: {\n            allowNull: false,\n            type: Sequelize.INTEGER,\n            defaultValue: 0,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n    const [[{ constraintExists }]] = await queryInterface.sequelize.query(`\n    SELECT COUNT(*) AS constraintExists\n    FROM information_schema.TABLE_CONSTRAINTS\n    WHERE TABLE_SCHEMA = DATABASE()\n      AND TABLE_NAME = 'paranet_kc'\n      AND CONSTRAINT_NAME = 'paranet_kc_ual_paranet_ual_uk';\n    `);\n\n    if (!constraintExists) {\n        await queryInterface.addConstraint('paranet_kc', {\n            fields: ['ual', 'paranet_ual'],\n            type: 'unique',\n            name: 'paranet_kc_ual_paranet_ual_uk', // Keep the default or a custom name\n        });\n    }\n\n    const [[{ indexExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS indexExists\n        FROM information_schema.statistics\n        WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'paranet_kc'\n          AND INDEX_NAME = 'idx_paranet_kc_sync_batch';\n    `);\n\n    if (!indexExists) {\n        await queryInterface.addIndex(\n            'paranet_kc',\n            ['paranet_ual', 'is_synced', 'retries', 'updated_at'],\n            { name: 'idx_paranet_kc_sync_batch' },\n        );\n    }\n\n    const [[{ triggerInsertExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS triggerInsertExists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE() AND trigger_name = 'after_insert_paranet_kc';\n    `);\n    if (triggerInsertExists === 0) {\n        await queryInterface.sequelize.query(`\n            CREATE TRIGGER after_insert_paranet_kc\n            BEFORE INSERT ON paranet_kc\n            FOR EACH ROW\n            BEGIN\n                SET NEW.created_at = NOW();\n            END;\n        `);\n    }\n\n    const [[{ triggerUpdateExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS triggerUpdateExists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE() AND trigger_name = 'after_update_paranet_kc';\n    `);\n    if (triggerUpdateExists === 0) {\n        await queryInterface.sequelize.query(`\n            CREATE TRIGGER after_update_paranet_kc\n            BEFORE UPDATE ON paranet_kc\n            FOR EACH ROW\n            BEGIN\n                SET NEW.updated_at = NOW();\n            END;\n        `);\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    const [[{ indexExists }]] = await queryInterface.sequelize.query(`\n    SELECT COUNT(*) AS indexExists\n    FROM information_schema.statistics\n    WHERE TABLE_SCHEMA = DATABASE()\n      AND TABLE_NAME = 'paranet_kc'\n      AND INDEX_NAME = 'idx_paranet_kc_sync_batch';\n    `);\n    if (indexExists) {\n        await queryInterface.removeIndex('paranet_kc', 'idx_paranet_kc_sync_batch');\n    }\n    await queryInterface.dropTable('paranet_kc');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20241226151800-prune-commands.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    await queryInterface.sequelize.query('TRUNCATE TABLE commands;');\n}\n\nexport async function down() {\n    // No need to do anything in the down method for truncation\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250401123500-truncate-commands-table.js",
    "content": "export async function up({ context: { queryInterface } }) {\n    await queryInterface.sequelize.query('TRUNCATE TABLE commands;');\n}\n\nexport async function down() {\n    // No need to do anything in the down method for truncation\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250401155600-create-random-sampling-chanalage.js",
    "content": "export const up = async ({ context: { queryInterface, Sequelize } }) => {\n    await queryInterface.createTable('random_sampling_challenge', {\n        id: {\n            autoIncrement: true,\n            primaryKey: true,\n            type: Sequelize.INTEGER,\n        },\n        blockchain_id: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        // start_date: {\n        //     allowNull: false,\n        //     type: Sequelize.DATE,\n        // },\n        // end_date: {\n        //     allowNull: false,\n        //     type: Sequelize.DATE,\n        // },\n        contract_address: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        knowledge_collection_id: {\n            allowNull: false,\n            type: Sequelize.INTEGER,\n        },\n        chunk_number: {\n            allowNull: false,\n            type: Sequelize.INTEGER,\n        },\n        active_proof_period_start_block: {\n            allowNull: false,\n            type: Sequelize.BIGINT,\n        },\n        epoch: {\n            allowNull: false,\n            type: Sequelize.INTEGER,\n        },\n        sent_successfully: {\n            allowNull: false,\n            type: Sequelize.BOOLEAN,\n            defaultValue: false,\n        },\n        finalized: {\n            allowNull: false,\n            type: Sequelize.BOOLEAN,\n            defaultValue: false,\n        },\n        score: {\n            allowNull: false,\n            type: Sequelize.BIGINT,\n            defaultValue: 0,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n\n    const [[{ indexExists: randomSamplingBlockchainIdEpochSentSuccessfullyIndexExists }]] =\n        await queryInterface.sequelize.query(`\n          SELECT COUNT(*) AS indexExists\n          FROM information_schema.statistics\n          WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'random_sampling_challenge'\n          AND INDEX_NAME = 'idx_rs_challenge_status';\n    `);\n\n    if (!randomSamplingBlockchainIdEpochSentSuccessfullyIndexExists) {\n        await queryInterface.addIndex(\n            'random_sampling_challenge',\n            ['blockchain_id', 'epoch', 'sent_successfully', 'updated_at'],\n            { name: 'idx_rs_challenge_status' },\n        );\n    }\n\n    const [[{ triggerInsertExists }]] = await queryInterface.sequelize.query(`\n      SELECT COUNT(*) AS triggerInsertExists\n      FROM information_schema.triggers\n      WHERE trigger_schema = DATABASE() AND trigger_name = 'after_insert_random_sampling_challenge';\n  `);\n    if (triggerInsertExists === 0) {\n        await queryInterface.sequelize.query(`\n          CREATE TRIGGER after_insert_random_sampling_challenge\n          BEFORE INSERT ON random_sampling_challenge\n          FOR EACH ROW\n          BEGIN\n              SET NEW.created_at = NOW();\n          END;\n      `);\n    }\n\n    const [[{ triggerUpdateExists }]] = await queryInterface.sequelize.query(`\n      SELECT COUNT(*) AS triggerUpdateExists\n      FROM information_schema.triggers\n      WHERE trigger_schema = DATABASE() AND trigger_name = 'after_update_random_sampling_challenge';\n  `);\n    if (triggerUpdateExists === 0) {\n        await queryInterface.sequelize.query(`\n          CREATE TRIGGER after_update_random_sampling_challenge\n          BEFORE UPDATE ON random_sampling_challenge\n          FOR EACH ROW\n          BEGIN\n              SET NEW.updated_at = NOW();\n          END;\n      `);\n    }\n};\n\nexport const down = async ({ context: { queryInterface } }) => {\n    const [[{ indexExists: randomSamplingBlockchainIdEpochSentSuccessfullyIndexExists }]] =\n        await queryInterface.sequelize.query(`\n          SELECT COUNT(*) AS indexExists\n          FROM information_schema.statistics\n          WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'random_sampling_challenge'\n          AND INDEX_NAME = 'idx_rs_challenge_status';\n        `);\n    if (randomSamplingBlockchainIdEpochSentSuccessfullyIndexExists) {\n        await queryInterface.removeIndex('random_sampling_challenge', 'idx_rs_challenge_status');\n    }\n    await queryInterface.dropTable('random_sampling_challenge');\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250408164300-create-triples-inserted-count-table.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('triples_insert_count', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        count: {\n            type: Sequelize.BIGINT,\n            allowNull: false,\n            defaultValue: 0,\n        },\n    });\n}\n\nexport async function down() {\n    // No need to do anything in the down method for truncation\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250422150500-add-tx-hash-blockchain-event.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (!(await columnExists('blockchain_event', 'tx_hash'))) {\n        await queryInterface.addColumn('blockchain_event', 'tx_hash', {\n            type: Sequelize.STRING,\n        });\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    async function columnExists(table, column) {\n        const tableDescription = await queryInterface.describeTable(table);\n        return Object.prototype.hasOwnProperty.call(tableDescription, column);\n    }\n\n    if (await columnExists('blockchain_event', 'tx_hash')) {\n        await queryInterface.removeColumn('blockchain_event', 'tx_hash');\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250509142900-create-batch-get.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('batch_get', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        operation_id: {\n            type: Sequelize.UUID,\n            allowNull: false,\n        },\n        status: {\n            allowNull: false,\n            type: Sequelize.STRING,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n}\nexport async function down({ context: { queryInterface } }) {\n    await queryInterface.dropTable('batch_get');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250509142901-create-latest-synced-kc.js",
    "content": "export async function up({ context: { queryInterface, Sequelize } }) {\n    await queryInterface.createTable('latest_synced_kc', {\n        id: {\n            type: Sequelize.INTEGER,\n            primaryKey: true,\n            autoIncrement: true,\n        },\n        blockchain: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        contract_address: {\n            type: Sequelize.STRING,\n            allowNull: false,\n        },\n        latest_synced_kc: {\n            type: Sequelize.INTEGER,\n            allowNull: false,\n            defaultValue: 0,\n        },\n        created_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n        updated_at: {\n            allowNull: false,\n            type: Sequelize.DATE,\n            defaultValue: Sequelize.literal('NOW()'),\n        },\n    });\n\n    const [[{ triggerInsertExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS triggerInsertExists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE() AND trigger_name = 'after_insert_latest_synced_kc';\n    `);\n    if (triggerInsertExists === 0) {\n        await queryInterface.sequelize.query(`\n            CREATE TRIGGER after_insert_latest_synced_kc\n            BEFORE INSERT ON latest_synced_kc\n            FOR EACH ROW\n            BEGIN\n                SET NEW.created_at = NOW();\n            END;\n        `);\n    }\n\n    const [[{ triggerUpdateExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS triggerUpdateExists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE() AND trigger_name = 'after_update_latest_synced_kc';\n    `);\n    if (triggerUpdateExists === 0) {\n        await queryInterface.sequelize.query(`\n            CREATE TRIGGER after_update_latest_synced_kc\n            BEFORE UPDATE ON latest_synced_kc\n            FOR EACH ROW\n            BEGIN\n                SET NEW.updated_at = NOW();\n            END;\n        `);\n    }\n    const [[{ blockchainContractAddressIndexExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS indexExists\n        FROM information_schema.statistics\n        WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'latest_synced_kc'\n          AND INDEX_NAME = 'idx_latest_synced_kc_blockchain_contract_address';\n    `);\n    if (!blockchainContractAddressIndexExists) {\n        await queryInterface.addIndex('latest_synced_kc', ['blockchain', 'contract_address'], {\n            unique: true,\n            name: 'idx_latest_synced_kc_blockchain_contract_address',\n        });\n    }\n\n    const [[{ blockchainIndexExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS indexExists\n        FROM information_schema.statistics\n        WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'latest_synced_kc'\n          AND INDEX_NAME = 'idx_latest_synced_kc_blockchain';\n    `);\n    if (!blockchainIndexExists) {\n        await queryInterface.addIndex('latest_synced_kc', ['blockchain'], {\n            name: 'idx_latest_synced_kc_blockchain',\n        });\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    const [[{ blockchainContractAddressIndexExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS indexExists\n        FROM information_schema.statistics\n        WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'latest_synced_kc'\n          AND INDEX_NAME = 'idx_latest_synced_kc_blockchain_contract_address';\n    `);\n    if (blockchainContractAddressIndexExists) {\n        await queryInterface.removeIndex(\n            'latest_synced_kc',\n            'idx_latest_synced_kc_blockchain_contract_address',\n        );\n    }\n\n    const [[{ blockchainIndexExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS indexExists\n        FROM information_schema.statistics\n        WHERE TABLE_SCHEMA = DATABASE()\n          AND TABLE_NAME = 'latest_synced_kc'\n          AND INDEX_NAME = 'idx_latest_synced_kc_blockchain';\n    `);\n    if (blockchainIndexExists) {\n        await queryInterface.removeIndex('latest_synced_kc', 'idx_latest_synced_kc_blockchain');\n    }\n\n    await queryInterface.dropTable('latest_synced_kc');\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/migrations/20250509142902-create-sync-missed-kc.js",
    "content": "/* eslint-disable no-await-in-loop */\nimport { NODE_ENVIRONMENTS } from '../../../../../constants/constants.js';\n\nexport async function up({ context: { queryInterface, Sequelize } }) {\n    const nodeEnv = process.env.NODE_ENV;\n    let blockchains = [];\n    if (nodeEnv === NODE_ENVIRONMENTS.DEVELOPMENT || nodeEnv === NODE_ENVIRONMENTS.TEST) {\n        blockchains = ['hardhat1', 'hardhat2'];\n    } else if (nodeEnv === NODE_ENVIRONMENTS.TESTNET || nodeEnv === NODE_ENVIRONMENTS.MAINNET) {\n        blockchains = ['otp', 'gnosis', 'base'];\n    } else {\n        throw new Error(`Invalid node environment: ${nodeEnv}`);\n    }\n    for (const blockchain of blockchains) {\n        // Check if table exists\n        const [[{ tableExists }]] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS tableExists\n            FROM information_schema.tables\n            WHERE table_schema = DATABASE() AND table_name = '${blockchain}_sync_missed_kc';\n        `);\n        if (tableExists === 0) {\n            await queryInterface.createTable(`${blockchain}_sync_missed_kc`, {\n                id: {\n                    type: Sequelize.INTEGER,\n                    primaryKey: true,\n                    autoIncrement: true,\n                },\n                kc_id: {\n                    type: Sequelize.INTEGER,\n                    allowNull: false,\n                },\n                contract_address: {\n                    type: Sequelize.STRING,\n                    allowNull: false,\n                },\n                synced: {\n                    type: Sequelize.BOOLEAN,\n                    allowNull: false,\n                    defaultValue: false,\n                },\n                sync_error: {\n                    type: Sequelize.STRING,\n                    allowNull: true,\n                },\n                retry_count: {\n                    type: Sequelize.INTEGER,\n                    allowNull: false,\n                    defaultValue: 0,\n                },\n                created_at: {\n                    allowNull: false,\n                    type: Sequelize.DATE,\n                    defaultValue: Sequelize.literal('NOW()'),\n                },\n                updated_at: {\n                    allowNull: false,\n                    type: Sequelize.DATE,\n                    defaultValue: Sequelize.literal('NOW()'),\n                },\n            });\n        }\n\n        const [[{ triggerInsertExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS triggerInsertExists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE() AND trigger_name = 'after_insert_${blockchain}_sync_missed_kc';\n    `);\n        if (triggerInsertExists === 0) {\n            await queryInterface.sequelize.query(`\n            CREATE TRIGGER after_insert_${blockchain}_sync_missed_kc\n            BEFORE INSERT ON ${blockchain}_sync_missed_kc\n            FOR EACH ROW\n            BEGIN\n                SET NEW.created_at = NOW();\n            END;\n        `);\n        }\n\n        const [[{ triggerUpdateExists }]] = await queryInterface.sequelize.query(`\n        SELECT COUNT(*) AS triggerUpdateExists\n        FROM information_schema.triggers\n        WHERE trigger_schema = DATABASE() AND trigger_name = 'after_update_${blockchain}_sync_missed_kc';\n    `);\n        if (triggerUpdateExists === 0) {\n            await queryInterface.sequelize.query(`\n            CREATE TRIGGER after_update_${blockchain}_sync_missed_kc\n            BEFORE UPDATE ON ${blockchain}_sync_missed_kc\n            FOR EACH ROW\n            BEGIN\n                SET NEW.updated_at = NOW();\n            END;\n        `);\n        }\n\n        const [[{ contractKcIdIndexExists }]] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS indexExists\n            FROM information_schema.statistics\n            WHERE TABLE_SCHEMA = DATABASE()\n              AND TABLE_NAME = '${blockchain}_sync_missed_kc'\n              AND INDEX_NAME = 'idx_${blockchain}_sync_missed_kc_contract_kc_id';\n        `);\n        if (!contractKcIdIndexExists) {\n            await queryInterface.addIndex(\n                `${blockchain}_sync_missed_kc`,\n                ['contract_address', 'kc_id'],\n                { name: `idx_${blockchain}_sync_missed_kc_contract_kc_id` },\n            );\n        }\n\n        const [[{ retryIndexExists }]] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS indexExists\n            FROM information_schema.statistics\n            WHERE TABLE_SCHEMA = DATABASE()\n              AND TABLE_NAME = '${blockchain}_sync_missed_kc'\n              AND INDEX_NAME = 'idx_${blockchain}_sync_missed_kc_retry_index';\n        `);\n\n        if (!retryIndexExists) {\n            await queryInterface.addIndex(\n                `${blockchain}_sync_missed_kc`,\n                ['contract_address', 'synced', 'updated_at', 'retry_count'],\n                { name: `idx_${blockchain}_sync_missed_kc_retry_index` },\n            );\n        }\n    }\n}\n\nexport async function down({ context: { queryInterface } }) {\n    const nodeEnv = process.env.NODE_ENV;\n    let blockchains = [];\n    if (nodeEnv === NODE_ENVIRONMENTS.DEVELOPMENT || nodeEnv === NODE_ENVIRONMENTS.TEST) {\n        blockchains = ['hardhat1:31337', 'hardhat2:31337'];\n    } else if (nodeEnv === NODE_ENVIRONMENTS.TESTNET || nodeEnv === NODE_ENVIRONMENTS.MAINNET) {\n        blockchains = ['otp', 'gnosis', 'base'];\n    } else {\n        throw new Error(`Invalid node environment: ${nodeEnv}`);\n    }\n    for (const blockchain of blockchains) {\n        const [[{ contractKcIdIndexExists }]] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS indexExists\n            FROM information_schema.statistics\n            WHERE TABLE_SCHEMA = DATABASE()\n              AND TABLE_NAME = '${blockchain}_sync_missed_kc'\n              AND INDEX_NAME = 'idx_${blockchain}_sync_missed_kc_contract_kc_id';\n        `);\n        if (contractKcIdIndexExists) {\n            await queryInterface.removeIndex(\n                `${blockchain}_sync_missed_kc`,\n                `idx_${blockchain}_sync_missed_kc_contract_kc_id`,\n            );\n        }\n\n        const [[{ retryIndexExists }]] = await queryInterface.sequelize.query(`\n            SELECT COUNT(*) AS indexExists\n            FROM information_schema.statistics\n            WHERE TABLE_SCHEMA = DATABASE()\n              AND TABLE_NAME = '${blockchain}_sync_missed_kc'\n              AND INDEX_NAME = 'idx_${blockchain}_sync_missed_kc_retry_index';\n        `);\n        if (retryIndexExists) {\n            await queryInterface.removeIndex(\n                `${blockchain}_sync_missed_kc`,\n                `idx_${blockchain}_sync_missed_kc_retry_index`,\n            );\n        }\n        await queryInterface.dropTable(`${blockchain}_sync_missed_kc`);\n    }\n}\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/ability.js",
    "content": "export default (sequelize, DataTypes) => {\n    const ability = sequelize.define(\n        'ability',\n        {\n            name: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    ability.associate = () => {\n        // define association here\n    };\n    return ability;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/ask-response.js",
    "content": "export default (sequelize, DataTypes) => {\n    const askResponse = sequelize.define(\n        'ask_response',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    askResponse.associate = () => {\n        // associations can be defined here\n    };\n    return askResponse;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/ask.js",
    "content": "export default (sequelize, DataTypes) => {\n    const ask = sequelize.define(\n        'ask',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    ask.associate = () => {\n        // associations can be defined here\n    };\n    return ask;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/base-sync-missed-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const baseSyncMissedKc = sequelize.define(\n        'base_sync_missed_kc',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            kcId: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                field: 'kc_id',\n            },\n            contractAddress: {\n                type: DataTypes.STRING,\n                allowNull: false,\n                field: 'contract_address',\n            },\n            synced: {\n                type: DataTypes.BOOLEAN,\n                allowNull: false,\n                defaultValue: false,\n            },\n            syncError: {\n                type: DataTypes.STRING,\n                allowNull: true,\n                field: 'sync_error',\n            },\n            retryCount: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                defaultValue: 0,\n                field: 'retry_count',\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n                field: 'created_at',\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n                field: 'updated_at',\n            },\n        },\n        { underscored: true },\n    );\n\n    baseSyncMissedKc.associate = () => {\n        // associations can be defined here\n    };\n\n    return baseSyncMissedKc;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/batch-get.js",
    "content": "export default (sequelize, DataTypes) => {\n    const batchGet = sequelize.define(\n        'batch_get',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    batchGet.associate = () => {\n        // associations can be defined here\n    };\n    return batchGet;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/blockchain-event.js",
    "content": "export default (sequelize, DataTypes) => {\n    const event = sequelize.define(\n        'blockchain_event',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            contract: DataTypes.STRING,\n            contractAddress: DataTypes.STRING,\n            blockchain: DataTypes.STRING,\n            event: DataTypes.STRING,\n            data: DataTypes.TEXT,\n            blockNumber: DataTypes.BIGINT,\n            transactionIndex: DataTypes.BIGINT,\n            txHash: DataTypes.STRING,\n            logIndex: DataTypes.BIGINT,\n            processed: DataTypes.BOOLEAN,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    event.associate = () => {\n        // associations can be defined here\n    };\n    return event;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/blockchain.js",
    "content": "export default (sequelize, DataTypes) => {\n    const blockchain = sequelize.define(\n        'blockchain',\n        {\n            blockchain: {\n                type: DataTypes.STRING,\n                primaryKey: true,\n            },\n            lastCheckedBlock: DataTypes.BIGINT,\n            lastCheckedTimestamp: DataTypes.BIGINT,\n        },\n        { underscored: true },\n    );\n    blockchain.associate = () => {\n        // associations can be defined here\n    };\n    return blockchain;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/commands.js",
    "content": "import { Model } from 'sequelize';\nimport { v4 as uuidv4 } from 'uuid';\n\nexport default (sequelize, DataTypes) => {\n    class commands extends Model {\n        static associate(models) {\n            commands._models = models;\n            // define association here\n        }\n    }\n    commands.init(\n        {\n            id: {\n                type: DataTypes.UUID,\n                primaryKey: true,\n                defaultValue: () => uuidv4(),\n            },\n            name: DataTypes.STRING,\n            data: DataTypes.JSON,\n            priority: DataTypes.BIGINT,\n            isBlocking: DataTypes.BOOLEAN,\n            sequence: DataTypes.JSON,\n            readyAt: DataTypes.BIGINT,\n            delay: DataTypes.BIGINT,\n            startedAt: DataTypes.BIGINT,\n            deadlineAt: DataTypes.BIGINT,\n            period: DataTypes.BIGINT,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            parentId: DataTypes.UUID,\n            transactional: DataTypes.BOOLEAN,\n            retries: {\n                type: DataTypes.INTEGER,\n                defaultValue: 0,\n            },\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        {\n            sequelize,\n            modelName: 'commands',\n            timestamps: false,\n            underscored: true,\n        },\n    );\n    return commands;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/event.js",
    "content": "export default (sequelize, DataTypes) => {\n    const event = sequelize.define(\n        'event',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            blockchainId: DataTypes.STRING,\n            name: DataTypes.STRING,\n            timestamp: DataTypes.STRING,\n            value1: DataTypes.TEXT,\n            value2: DataTypes.TEXT,\n            value3: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    event.associate = () => {\n        // associations can be defined here\n    };\n    return event;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/finality-response.js",
    "content": "export default (sequelize, DataTypes) => {\n    const finalityResponse = sequelize.define(\n        'finality_response',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    finalityResponse.associate = () => {\n        // associations can be defined here\n    };\n    return finalityResponse;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/finality-status.js",
    "content": "export default (sequelize, DataTypes) => {\n    const finalityStatus = sequelize.define(\n        'finality_status',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.STRING,\n            ual: DataTypes.STRING,\n            peerId: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    finalityStatus.associate = () => {\n        // associations can be defined here\n    };\n    return finalityStatus;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/finality.js",
    "content": "export default (sequelize, DataTypes) => {\n    const finality = sequelize.define(\n        'finality',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    finality.associate = () => {\n        // associations can be defined here\n    };\n    return finality;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/get-response.js",
    "content": "export default (sequelize, DataTypes) => {\n    const getResponse = sequelize.define(\n        'get_response',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    getResponse.associate = () => {\n        // associations can be defined here\n    };\n    return getResponse;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/get.js",
    "content": "export default (sequelize, DataTypes) => {\n    const get = sequelize.define(\n        'get',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    get.associate = () => {\n        // associations can be defined here\n    };\n    return get;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/gnosis-sync-missed-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const gnosisSyncMissedKc = sequelize.define(\n        'gnosis_sync_missed_kc',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            kcId: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                field: 'kc_id',\n            },\n            contractAddress: {\n                type: DataTypes.STRING,\n                allowNull: false,\n                field: 'contract_address',\n            },\n            synced: {\n                type: DataTypes.BOOLEAN,\n                allowNull: false,\n                defaultValue: false,\n            },\n            syncError: {\n                type: DataTypes.STRING,\n                allowNull: true,\n                field: 'sync_error',\n            },\n            retryCount: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                defaultValue: 0,\n                field: 'retry_count',\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n                field: 'created_at',\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n                field: 'updated_at',\n            },\n        },\n        { underscored: true },\n    );\n\n    gnosisSyncMissedKc.associate = () => {\n        // associations can be defined here\n    };\n\n    return gnosisSyncMissedKc;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/hardhat1-sync-missed-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const hardhat1SyncMissedKc = sequelize.define(\n        'hardhat1_sync_missed_kc',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            kcId: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                field: 'kc_id',\n            },\n            contractAddress: {\n                type: DataTypes.STRING,\n                allowNull: false,\n                field: 'contract_address',\n            },\n            synced: {\n                type: DataTypes.BOOLEAN,\n                allowNull: false,\n                defaultValue: false,\n            },\n            syncError: {\n                type: DataTypes.STRING,\n                allowNull: true,\n                field: 'sync_error',\n            },\n            retryCount: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                defaultValue: 0,\n                field: 'retry_count',\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n                field: 'created_at',\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n                field: 'updated_at',\n            },\n        },\n        { underscored: true },\n    );\n\n    hardhat1SyncMissedKc.associate = () => {\n        // associations can be defined here\n    };\n\n    return hardhat1SyncMissedKc;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/hardhat2-sync-missed-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const hardhat2SyncMissedKc = sequelize.define(\n        'hardhat2_sync_missed_kc',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            kcId: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                field: 'kc_id',\n            },\n            contractAddress: {\n                type: DataTypes.STRING,\n                allowNull: false,\n                field: 'contract_address',\n            },\n            synced: {\n                type: DataTypes.BOOLEAN,\n                allowNull: false,\n                defaultValue: false,\n            },\n            syncError: {\n                type: DataTypes.STRING,\n                allowNull: true,\n                field: 'sync_error',\n            },\n            retryCount: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                defaultValue: 0,\n                field: 'retry_count',\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n                field: 'created_at',\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n                field: 'updated_at',\n            },\n        },\n        { underscored: true },\n    );\n\n    hardhat2SyncMissedKc.associate = () => {\n        // associations can be defined here\n    };\n\n    return hardhat2SyncMissedKc;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/latest-synced-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const latestSyncedKc = sequelize.define(\n        'latest_synced_kc',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            blockchain: {\n                type: DataTypes.STRING,\n                allowNull: false,\n            },\n            contractAddress: {\n                type: DataTypes.STRING,\n                allowNull: false,\n                field: 'contract_address',\n            },\n            latestSyncedKc: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                field: 'latest_synced_kc',\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n                field: 'created_at',\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n                field: 'updated_at',\n            },\n        },\n        { underscored: true },\n    );\n\n    latestSyncedKc.associate = () => {\n        // associations can be defined here\n    };\n\n    return latestSyncedKc;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/missed-paranet-asset.js",
    "content": "// NOT USED ANYMORE\n\nexport default (sequelize, DataTypes) => {\n    const blockchain = sequelize.define(\n        'missed_paranet_asset',\n        {\n            id: {\n                autoIncrement: true,\n                primaryKey: true,\n                type: DataTypes.INTEGER,\n            },\n            blockchainId: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            ual: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            paranetUal: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            errorMessage: {\n                allowNull: true,\n                type: DataTypes.TEXT,\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n            },\n        },\n        { underscored: true },\n    );\n    blockchain.associate = () => {\n        // associations can be defined here\n    };\n    return blockchain;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/operation_ids.js",
    "content": "import { v4 as uuidv4 } from 'uuid';\n\nexport default (sequelize, DataTypes) => {\n    const operationIds = sequelize.define(\n        'operation_ids',\n        {\n            operationId: {\n                type: DataTypes.UUID,\n                primaryKey: true,\n                defaultValue: () => uuidv4(),\n            },\n            data: DataTypes.TEXT,\n            status: DataTypes.STRING,\n            minAcksReached: DataTypes.BOOLEAN,\n            timestamp: {\n                type: DataTypes.BIGINT,\n                defaultValue: () => Date.now(),\n            },\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    operationIds.associate = () => {\n        // associations can be defined here\n    };\n    return operationIds;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/otp-sync-missed-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const otpSyncMissedKc = sequelize.define(\n        'otp_sync_missed_kc',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            kcId: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                field: 'kc_id',\n            },\n            contractAddress: {\n                type: DataTypes.STRING,\n                allowNull: false,\n                field: 'contract_address',\n            },\n            synced: {\n                type: DataTypes.BOOLEAN,\n                allowNull: false,\n                defaultValue: false,\n            },\n            syncError: {\n                type: DataTypes.STRING,\n                allowNull: true,\n                field: 'sync_error',\n            },\n            retryCount: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n                defaultValue: 0,\n                field: 'retry_count',\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n                field: 'created_at',\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n                field: 'updated_at',\n            },\n        },\n        { underscored: true },\n    );\n\n    otpSyncMissedKc.associate = () => {\n        // associations can be defined here\n    };\n\n    return otpSyncMissedKc;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/paranet-kc.js",
    "content": "export default (sequelize, DataTypes) => {\n    const paranetKC = sequelize.define(\n        'paranet_kc',\n        {\n            id: {\n                autoIncrement: true,\n                primaryKey: true,\n                type: DataTypes.INTEGER,\n            },\n            blockchainId: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            ual: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            paranetUal: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            errorMessage: {\n                allowNull: true,\n                type: DataTypes.TEXT,\n            },\n            isSynced: {\n                allowNull: false,\n                type: DataTypes.BOOLEAN,\n                defaultValue: false,\n            },\n            retries: {\n                allowNull: false,\n                type: DataTypes.INTEGER,\n                defaultValue: 0,\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n            },\n        },\n        {\n            underscored: true,\n            indexes: [\n                {\n                    unique: true,\n                    fields: ['ual', 'paranetUal'], // Composite unique constraint on `ual` and `paranetUal`\n                },\n                {\n                    fields: ['paranetUal', 'isSynced', 'retries', 'updatedAt'],\n                },\n            ],\n        },\n    );\n\n    paranetKC.associate = () => {\n        // Define associations here if needed\n    };\n\n    return paranetKC;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/paranet-synced-asset.js",
    "content": "import { PARANET_SYNC_SOURCES } from '../../../../../constants/constants.js';\n\n// NOT USED ANYMORE\nexport default (sequelize, DataTypes) => {\n    const blockchain = sequelize.define(\n        'paranet_synced_asset',\n        {\n            id: {\n                autoIncrement: true,\n                primaryKey: true,\n                type: DataTypes.INTEGER,\n            },\n            blockchainId: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            ual: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            paranetUal: {\n                allowNull: false,\n                type: DataTypes.STRING,\n            },\n            publicAssertionId: {\n                allowNull: true,\n                type: DataTypes.STRING,\n            },\n            privateAssertionId: {\n                allowNull: true,\n                type: DataTypes.STRING,\n            },\n            sender: {\n                allowNull: true,\n                type: DataTypes.STRING,\n            },\n            transactionHash: {\n                allowNull: true,\n                type: DataTypes.STRING,\n            },\n            dataSource: {\n                allowNull: true,\n                type: DataTypes.ENUM(...Object.values(PARANET_SYNC_SOURCES)),\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n            },\n        },\n        { underscored: true },\n    );\n    blockchain.associate = () => {\n        // associations can be defined here\n    };\n    return blockchain;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/paranet.js",
    "content": "export default (sequelize, DataTypes) => {\n    const paranet = sequelize.define(\n        'paranet',\n        {\n            id: {\n                autoIncrement: true,\n                primaryKey: true,\n                type: DataTypes.INTEGER,\n            },\n            name: {\n                type: DataTypes.STRING,\n            },\n            description: {\n                type: DataTypes.STRING,\n            },\n            paranetId: {\n                type: DataTypes.STRING,\n            },\n            kaCount: {\n                type: DataTypes.INTEGER,\n            },\n            blockchainId: {\n                type: DataTypes.STRING,\n            },\n            createdAt: {\n                type: DataTypes.DATE,\n            },\n            updatedAt: {\n                type: DataTypes.DATE,\n            },\n        },\n        { underscored: true },\n    );\n    paranet.associate = () => {\n        // associations can be defined here\n    };\n    return paranet;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/publish-paranet-response.js",
    "content": "export default (sequelize, DataTypes) => {\n    const publishParanetResponse = sequelize.define(\n        'publish_paranet_response',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    publishParanetResponse.associate = () => {\n        // associations can be defined here\n    };\n    return publishParanetResponse;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/publish-paranet.js",
    "content": "export default (sequelize, DataTypes) => {\n    const publishParanet = sequelize.define(\n        'publish_paranet',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    publishParanet.associate = () => {\n        // associations can be defined here\n    };\n    return publishParanet;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/publish-response.js",
    "content": "export default (sequelize, DataTypes) => {\n    const publishResponse = sequelize.define(\n        'publish_response',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    publishResponse.associate = () => {\n        // associations can be defined here\n    };\n    return publishResponse;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/publish.js",
    "content": "export default (sequelize, DataTypes) => {\n    const publish = sequelize.define(\n        'publish',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    publish.associate = () => {\n        // associations can be defined here\n    };\n    return publish;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/random-sampling-challenge.js",
    "content": "export default (sequelize, DataTypes) => {\n    const randomSamplingChallenge = sequelize.define(\n        'random_sampling_challenge',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            blockchainId: DataTypes.STRING,\n            // startDate: DataTypes.DATE,\n            // endDate: DataTypes.DATE,\n            contractAddress: DataTypes.STRING,\n            knowledgeCollectionId: DataTypes.INTEGER,\n            chunkNumber: DataTypes.INTEGER,\n            epoch: DataTypes.INTEGER,\n            activeProofPeriodStartBlock: DataTypes.BIGINT,\n            finalized: DataTypes.BOOLEAN,\n            sentSuccessfully: DataTypes.BOOLEAN,\n            score: DataTypes.BIGINT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    randomSamplingChallenge.associate = () => {\n        // associations can be defined here\n    };\n    return randomSamplingChallenge;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/role-ability.js",
    "content": "export default (sequelize, DataTypes) => {\n    const roleAbility = sequelize.define(\n        'role_ability',\n        {\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    roleAbility.associate = (models) => {\n        roleAbility.hasOne(models.ability, { as: 'ability' });\n        roleAbility.hasOne(models.role, { as: 'role' });\n    };\n    return roleAbility;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/role.js",
    "content": "export default (sequelize, DataTypes) => {\n    const role = sequelize.define(\n        'role',\n        {\n            name: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    role.associate = (models) => {\n        role.belongsToMany(models.ability, {\n            as: 'abilities',\n            foreignKey: 'ability_id',\n            through: models.role_ability,\n        });\n    };\n    return role;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/shard.js",
    "content": "export default (sequelize, DataTypes) => {\n    const shard = sequelize.define(\n        'shard',\n        {\n            peerId: { type: DataTypes.STRING, primaryKey: true },\n            blockchainId: { type: DataTypes.STRING, primaryKey: true },\n            ask: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n            },\n            stake: {\n                type: DataTypes.INTEGER,\n                allowNull: false,\n            },\n            lastSeen: {\n                type: DataTypes.DATE,\n                allowNull: false,\n                defaultValue: new Date(0),\n            },\n            lastDialed: {\n                type: DataTypes.DATE,\n                allowNull: false,\n                defaultValue: new Date(0),\n            },\n            sha256: {\n                type: DataTypes.STRING,\n                allowNull: false,\n            },\n        },\n        { underscored: true },\n    );\n    shard.associate = () => {\n        // associations can be defined here\n    };\n    return shard;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/token.js",
    "content": "export default (sequelize, DataTypes) => {\n    const token = sequelize.define(\n        'token',\n        {\n            id: { type: DataTypes.STRING, primaryKey: true },\n            revoked: DataTypes.BOOLEAN,\n            userId: DataTypes.INTEGER,\n            name: {\n                type: DataTypes.STRING,\n            },\n            expiresAt: DataTypes.DATE,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    token.associate = (models) => {\n        token.belongsTo(models.user, { as: 'user' });\n    };\n    return token;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/triples-inserted-count.js",
    "content": "export default (sequelize, DataTypes) => {\n    const TriplesInsertCount = sequelize.define(\n        'triples_insert_count',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            count: {\n                type: DataTypes.BIGINT,\n                allowNull: false,\n                defaultValue: 0,\n            },\n        },\n        {\n            timestamps: false,\n            freezeTableName: true,\n        },\n    );\n\n    return TriplesInsertCount;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/update-response.js",
    "content": "export default (sequelize, DataTypes) => {\n    const updateResponse = sequelize.define(\n        'update_response',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            message: DataTypes.TEXT,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    updateResponse.associate = () => {\n        // associations can be defined here\n    };\n    return updateResponse;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/update.js",
    "content": "export default (sequelize, DataTypes) => {\n    const update = sequelize.define(\n        'update',\n        {\n            id: {\n                type: DataTypes.INTEGER,\n                primaryKey: true,\n                autoIncrement: true,\n            },\n            operationId: DataTypes.UUID,\n            status: DataTypes.STRING,\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    update.associate = () => {\n        // associations can be defined here\n    };\n    return update;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/models/user.js",
    "content": "export default (sequelize, DataTypes) => {\n    const user = sequelize.define(\n        'user',\n        {\n            name: {\n                type: DataTypes.STRING,\n                unique: true,\n            },\n            createdAt: DataTypes.DATE,\n            updatedAt: DataTypes.DATE,\n        },\n        { underscored: true },\n    );\n    user.associate = (models) => {\n        user.hasMany(models.token, { as: 'tokens' });\n        user.hasOne(models.role, { as: 'role' });\n    };\n    return user;\n};\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/blockchain-event-repository.js",
    "content": "import Sequelize from 'sequelize';\n\nclass BlockchainEventRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.blockchain_event;\n    }\n\n    async insertBlockchainEvents(events, options) {\n        const chunkSize = 10000;\n        let insertedEvents = [];\n\n        for (let i = 0; i < events.length; i += chunkSize) {\n            const chunk = events.slice(i, i + chunkSize);\n            // eslint-disable-next-line no-await-in-loop\n            const insertedChunk = await this.model.bulkCreate(\n                chunk.map((event) => ({\n                    blockchain: event.blockchain,\n                    contract: event.contract,\n                    contractAddress: event.contractAddress,\n                    event: event.event,\n                    data: event.data,\n                    blockNumber: event.blockNumber,\n                    transactionIndex: event.transactionIndex,\n                    logIndex: event.logIndex,\n                    processed: false,\n                    txHash: event.txHash,\n                })),\n                {\n                    ignoreDuplicates: true,\n                    ...options,\n                },\n            );\n\n            insertedEvents = insertedEvents.concat(insertedChunk.map((event) => event.dataValues));\n        }\n\n        return insertedEvents;\n    }\n\n    async getAllUnprocessedBlockchainEvents(blockchain, eventNames, options) {\n        return this.model.findAll({\n            where: {\n                blockchain,\n                processed: false,\n                event: { [Sequelize.Op.in]: eventNames },\n            },\n            order: [\n                ['blockNumber', 'asc'],\n                ['transactionIndex', 'asc'],\n                ['logIndex', 'asc'],\n            ],\n            ...options,\n        });\n    }\n\n    async markAllBlockchainEventsAsProcessed(blockchain, options) {\n        return this.model.update(\n            { processed: true },\n            {\n                where: { blockchain, processed: false },\n                ...options,\n            },\n        );\n    }\n\n    async removeEvents(ids, options) {\n        await this.model.destroy({\n            where: {\n                id: { [Sequelize.Op.in]: ids },\n            },\n            ...options,\n        });\n    }\n\n    async removeContractEventsAfterBlock(\n        blockchain,\n        contract,\n        contractAddress,\n        blockNumber,\n        transactionIndex,\n        options,\n    ) {\n        return this.model.destroy({\n            where: {\n                blockchain,\n                contract,\n                contractAddress,\n                [Sequelize.Op.or]: [\n                    // Events in blocks after the given blockNumber\n                    { blockNumber: { [Sequelize.Op.gt]: blockNumber } },\n                    // Events in the same blockNumber but with a higher transactionIndex\n                    {\n                        blockNumber,\n                        transactionIndex: { [Sequelize.Op.gt]: transactionIndex },\n                    },\n                ],\n            },\n            ...options,\n        });\n    }\n\n    async findAndRemoveProcessedEvents(timestamp, limit, options) {\n        return this.model.destroy({\n            where: {\n                processed: true,\n                createdAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            limit,\n            ...options,\n        });\n    }\n\n    async findProcessedEvents(timestamp, limit, options) {\n        return this.model.findAll({\n            where: {\n                processed: true,\n                createdAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            order: [['createdAt', 'asc']],\n            raw: true,\n            limit,\n            ...options,\n        });\n    }\n}\n\nexport default BlockchainEventRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/blockchain-missed-kc-repository.js",
    "content": "import Sequelize from 'sequelize';\nimport { NODE_ENVIRONMENTS } from '../../../../../constants/constants.js';\n\nclass BlockchainMissedKcRepository {\n    constructor(models) {\n        const nodeEnv = process.env.NODE_ENV;\n        if (nodeEnv === NODE_ENVIRONMENTS.DEVELOPMENT || nodeEnv === NODE_ENVIRONMENTS.TEST) {\n            this.models = {\n                hardhat1: models.hardhat1_sync_missed_kc,\n                hardhat2: models.hardhat2_sync_missed_kc,\n            };\n        } else if (nodeEnv === NODE_ENVIRONMENTS.TESTNET || nodeEnv === NODE_ENVIRONMENTS.MAINNET) {\n            this.models = {\n                otp: models.otp_sync_missed_kc,\n                gnosis: models.gnosis_sync_missed_kc,\n                base: models.base_sync_missed_kc,\n            };\n        } else {\n            throw new Error(`Invalid node environment: ${nodeEnv}`);\n        }\n    }\n\n    async insertMissedKc(blockchain, records, error, options) {\n        const blockchainName = blockchain.split(':')[0];\n        const model = this.models[blockchainName];\n        const query = `\n            INSERT INTO ${blockchainName}_sync_missed_kc (kc_id, contract_address, sync_error)\n            VALUES ${records\n                .map((record) => `('${record.kcId}', '${record.contractAddress}', '${error}')`)\n                .join(',')}\n        `;\n        return model.sequelize.query(query, {\n            type: model.sequelize.QueryTypes.INSERT,\n            ...options,\n        });\n    }\n\n    async getMissedKcForRetry(blockchain, contractAddress, limit, options) {\n        const blockchainName = blockchain.split(':')[0];\n        const model = this.models[blockchainName];\n\n        return model.findAll({\n            where: {\n                contract_address: contractAddress,\n                synced: false,\n                [Sequelize.Op.and]: [\n                    Sequelize.literal(`\n                        NOW() >= LEAST(\n                            DATE_ADD(updated_at, INTERVAL POW(2, retry_count) MINUTE),\n                            DATE_ADD(updated_at, INTERVAL 7 DAY)\n                        )\n                    `),\n                ],\n            },\n            limit,\n            ...options,\n        });\n    }\n\n    async incrementRetryCount(blockchain, records, options) {\n        const blockchainName = blockchain.split(':')[0];\n\n        const kcIds = [...new Set(records.map((r) => r.kcId))];\n        const contractAddresses = [...new Set(records.map((r) => r.contractAddress))];\n\n        const model = this.models[blockchainName];\n        const query = `\n            UPDATE ${blockchainName}_sync_missed_kc\n            SET retry_count = retry_count + 1\n            WHERE kc_id IN (:kcIds)\n            AND contract_address IN (:contractAddresses)\n        `;\n\n        return model.sequelize.query(query, {\n            replacements: {\n                kcIds,\n                contractAddresses,\n                blockchainId: blockchain,\n            },\n            type: model.sequelize.QueryTypes.UPDATE,\n            ...options,\n        });\n    }\n\n    async setSyncedToTrue(blockchain, records, options) {\n        const blockchainName = blockchain.split(':')[0];\n\n        const kcIds = [...new Set(records.map((r) => r.kcId))];\n        const contractAddresses = [...new Set(records.map((r) => r.contractAddress))];\n\n        const model = this.models[blockchainName];\n        const query = `\n            UPDATE ${blockchainName}_sync_missed_kc\n            SET synced = true\n            WHERE kc_id IN (:kcIds)\n            AND contract_address IN (:contractAddresses)\n        `;\n\n        return model.sequelize.query(query, {\n            replacements: {\n                kcIds,\n                contractAddresses,\n                blockchainId: blockchain,\n            },\n            type: model.sequelize.QueryTypes.UPDATE,\n            ...options,\n        });\n    }\n\n    async getMissedKcForRetryCount(blockchain, contractAddress, options) {\n        const blockchainName = blockchain.split(':')[0];\n        const model = this.models[blockchainName];\n\n        return model.count({\n            where: { contract_address: contractAddress, synced: false },\n            ...options,\n        });\n    }\n}\n\nexport default BlockchainMissedKcRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/blockchain-repository.js",
    "content": "class BlockchainRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.blockchain;\n    }\n\n    async getLastCheckedBlock(blockchain, options) {\n        return this.model.findOne({\n            where: { blockchain },\n            ...options,\n        });\n    }\n\n    async updateLastCheckedBlock(blockchain, currentBlock, timestamp, options) {\n        return this.model.upsert(\n            {\n                blockchain,\n                lastCheckedBlock: currentBlock,\n                lastCheckedTimestamp: timestamp,\n            },\n            options,\n        );\n    }\n}\n\nexport default BlockchainRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/command-repository.js",
    "content": "import Sequelize from 'sequelize';\nimport { COMMAND_STATUS } from '../../../../../constants/constants.js';\n\nclass CommandRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.commands;\n    }\n\n    async updateCommand(update, options) {\n        await this.model.update(update, options);\n    }\n\n    async destroyCommand(name, options) {\n        await this.model.destroy({\n            where: {\n                name: { [Sequelize.Op.eq]: name },\n            },\n            ...options,\n        });\n    }\n\n    async createCommand(command, options) {\n        return this.model.create(command, options);\n    }\n\n    async getCommandsWithStatus(statusArray, excludeNameArray, options) {\n        return this.model.findAll({\n            where: {\n                status: {\n                    [Sequelize.Op.in]: statusArray,\n                },\n                name: { [Sequelize.Op.notIn]: excludeNameArray },\n            },\n            ...options,\n        });\n    }\n\n    async getCommandWithId(id, options) {\n        return this.model.findOne({\n            where: {\n                id,\n            },\n            ...options,\n        });\n    }\n\n    async removeCommands(ids, options) {\n        await this.model.destroy({\n            where: {\n                id: { [Sequelize.Op.in]: ids },\n            },\n            ...options,\n        });\n    }\n\n    async findFinalizedCommands(timestamp, limit, options) {\n        return this.model.findAll({\n            where: {\n                status: {\n                    [Sequelize.Op.in]: [\n                        COMMAND_STATUS.COMPLETED,\n                        COMMAND_STATUS.FAILED,\n                        COMMAND_STATUS.EXPIRED,\n                        COMMAND_STATUS.UNKNOWN,\n                    ],\n                },\n                startedAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            order: [['startedAt', 'asc']],\n            raw: true,\n            limit,\n            ...options,\n        });\n    }\n\n    async findAndRemoveFinalizedCommands(timestamp, limit, options) {\n        return this.model.destroy({\n            where: {\n                [Sequelize.Op.or]: [\n                    {\n                        status: {\n                            [Sequelize.Op.in]: [\n                                COMMAND_STATUS.COMPLETED,\n                                COMMAND_STATUS.FAILED,\n                                COMMAND_STATUS.EXPIRED,\n                                COMMAND_STATUS.UNKNOWN,\n                            ],\n                        },\n                    },\n                    {\n                        startedAt: { [Sequelize.Op.lte]: timestamp },\n                    },\n                ],\n            },\n            limit,\n            ...options,\n        });\n    }\n\n    async findUnfinalizedCommandsByName(name, options) {\n        return this.model.findAll({\n            where: {\n                name,\n                status: {\n                    [Sequelize.Op.notIn]: [\n                        COMMAND_STATUS.COMPLETED,\n                        COMMAND_STATUS.FAILED,\n                        COMMAND_STATUS.EXPIRED,\n                        COMMAND_STATUS.UNKNOWN,\n                    ],\n                },\n            },\n            raw: true,\n            ...options,\n        });\n    }\n}\n\nexport default CommandRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/event-repository.js",
    "content": "import Sequelize from 'sequelize';\nimport {\n    OPERATION_ID_STATUS,\n    HIGH_TRAFFIC_OPERATIONS_NUMBER_PER_HOUR,\n    SEND_TELEMETRY_COMMAND_FREQUENCY_MINUTES,\n} from '../../../../../constants/constants.js';\n\nclass EventRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.event;\n    }\n\n    async createEventRecord(\n        operationId,\n        blockchainId,\n        name,\n        timestamp,\n        value1,\n        value2,\n        value3,\n        options,\n    ) {\n        return this.model.create(\n            {\n                operationId,\n                blockchainId,\n                name,\n                timestamp,\n                value1,\n                value2,\n                value3,\n            },\n            options,\n        );\n    }\n\n    async getUnpublishedEvents(options) {\n        // events without COMPLETE/FAILED status which are older than 30min\n        // are also considered finished\n        const minutes = 5;\n\n        let operationIds = await this.model.findAll({\n            raw: true,\n            attributes: [\n                Sequelize.fn('DISTINCT', Sequelize.col('operation_id')),\n                Sequelize.col('timestamp'),\n            ],\n            where: {\n                [Sequelize.Op.or]: {\n                    name: {\n                        [Sequelize.Op.in]: [\n                            OPERATION_ID_STATUS.COMPLETED,\n                            OPERATION_ID_STATUS.FAILED,\n                        ],\n                    },\n                    timestamp: {\n                        [Sequelize.Op.lt]: Sequelize.literal(\n                            `(UNIX_TIMESTAMP()*1000 - 1000*60*${minutes})`,\n                        ),\n                    },\n                },\n            },\n            order: [['timestamp', 'asc']],\n            limit:\n                Math.floor(HIGH_TRAFFIC_OPERATIONS_NUMBER_PER_HOUR / 60) *\n                SEND_TELEMETRY_COMMAND_FREQUENCY_MINUTES,\n            ...options,\n        });\n\n        operationIds = operationIds.map((e) => e.operation_id);\n\n        return this.model.findAll({\n            where: {\n                operationId: {\n                    [Sequelize.Op.in]: operationIds,\n                },\n            },\n            ...options,\n        });\n    }\n\n    async destroyEvents(ids, options) {\n        await this.model.destroy({\n            where: {\n                id: {\n                    [Sequelize.Op.in]: ids,\n                },\n            },\n            ...options,\n        });\n    }\n}\n\nexport default EventRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/finality-status-repository.js",
    "content": "class FinalityStatusRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.finality_status;\n    }\n\n    async getFinalityAcksCount(ual, options) {\n        return this.model.count({\n            where: { ual },\n            ...options,\n        });\n    }\n\n    async saveFinalityAck(operationId, ual, peerId, options) {\n        return this.model.upsert({ operationId, ual, peerId }, options);\n    }\n\n    async getPublishOperationIdByUal(ual, options) {\n        const record = await this.model.findOne({\n            where: { ual },\n            attributes: ['operationId'],\n            ...options,\n        });\n        return record?.operationId ?? null;\n    }\n}\n\nexport default FinalityStatusRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/inserted-triples-repository.js",
    "content": "import { Sequelize } from 'sequelize';\n\nclass TriplesInsertCountRepository {\n    constructor(models) {\n        this.model = models.triples_insert_count;\n    }\n\n    async getCount() {\n        const record = await this.model.findOne();\n        return record?.count || 0;\n    }\n\n    async increment(by = 1, options = {}) {\n        const [record] = await this.model.findOrCreate({\n            where: {},\n            defaults: { count: 0 },\n            ...options,\n        });\n\n        await this.model.update(\n            {\n                count: Sequelize.literal(`count + ${by}`),\n            },\n            {\n                where: { id: record.id },\n                ...options,\n            },\n        );\n    }\n}\n\nexport default TriplesInsertCountRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/latest-synced-kc-repository.js",
    "content": "class LatestSyncedKcRepository {\n    constructor(ctx) {\n        this.model = ctx.latest_synced_kc;\n    }\n\n    async getKCStorageContracts(blockchainId) {\n        return this.model.findAll({\n            attributes: ['contract_address'],\n            where: { blockchain: blockchainId },\n        });\n    }\n\n    getSyncRecordForBlockchain(blockchainId) {\n        return this.model.findAll({\n            where: { blockchain: blockchainId },\n        });\n    }\n\n    async addSyncContracts(blockchainId, contracts) {\n        const query = `\n            INSERT INTO latest_synced_kc (blockchain, contract_address)\n            VALUES ${contracts.map((contract) => `('${blockchainId}', '${contract}')`).join(',')}\n        `;\n\n        return this.model.sequelize.query(query, { type: this.model.sequelize.QueryTypes.INSERT });\n    }\n\n    async updateLatestSyncedKc(blockchainId, contractAddress, latestSyncedKc, options) {\n        return this.model.update(\n            { latestSyncedKc },\n            { where: { blockchain: blockchainId, contractAddress }, ...options },\n        );\n    }\n}\n\nexport default LatestSyncedKcRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/missed-paranet-asset-repository.js",
    "content": "import Sequelize from 'sequelize';\n\nclass MissedParanetAssetRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.missed_paranet_asset;\n    }\n\n    async createMissedParanetAssetRecord(missedParanetAsset, options) {\n        return this.model.create(missedParanetAsset, options);\n    }\n\n    async getMissedParanetAssetsRecordsWithRetryCount(\n        paranetUal,\n        retryCountLimit,\n        retryDelayInMs,\n        limit,\n        options,\n    ) {\n        const now = new Date();\n        const delayDate = new Date(now.getTime() - retryDelayInMs);\n\n        const queryOptions = {\n            attributes: [\n                'blockchainId',\n                'ual',\n                'paranetUal',\n                [Sequelize.fn('MAX', Sequelize.col('created_at')), 'latestCreatedAt'],\n                [Sequelize.fn('COUNT', Sequelize.col('ual')), 'retryCount'],\n            ],\n            where: {\n                paranetUal,\n            },\n            group: ['ual', 'blockchainId', 'paranetUal'],\n            having: Sequelize.and(\n                Sequelize.literal(`COUNT(ual) < ${retryCountLimit}`),\n                Sequelize.literal(`MAX(created_at) <= '${delayDate.toISOString()}'`),\n            ),\n            ...options,\n        };\n\n        if (limit !== null) {\n            queryOptions.limit = limit;\n        }\n\n        return this.model.findAll(queryOptions);\n    }\n\n    async missedParanetAssetRecordExists(ual, options) {\n        const missedParanetAssetRecord = await this.model.findOne({\n            where: { ual },\n            ...options,\n        });\n\n        return !!missedParanetAssetRecord;\n    }\n\n    async removeMissedParanetAssetRecordsByUAL(ual, options) {\n        await this.model.destroy({\n            where: {\n                ual,\n            },\n            ...options,\n        });\n    }\n\n    async getCountOfMissedAssetsOfParanet(paranetUal, options) {\n        const records = await this.model.findAll({\n            attributes: ['paranet_ual', 'ual'],\n            where: {\n                paranetUal,\n            },\n            group: ['paranet_ual', 'ual'],\n            ...options,\n        });\n\n        return records.length;\n    }\n\n    async getFilteredCountOfMissedAssetsOfParanet(\n        paranetUal,\n        retryCountLimit,\n        retryDelayInMs,\n        options,\n    ) {\n        const now = new Date();\n        const delayDate = new Date(now.getTime() - retryDelayInMs);\n\n        const records = await this.model.findAll({\n            attributes: [\n                [Sequelize.fn('MAX', Sequelize.col('created_at')), 'latestCreatedAt'],\n                [Sequelize.fn('COUNT', Sequelize.col('ual')), 'retryCount'],\n            ],\n            where: {\n                paranetUal,\n            },\n            group: ['paranet_ual', 'ual'],\n            having: {\n                retryCount: {\n                    [Sequelize.Op.lt]: retryCountLimit,\n                },\n                latestCreatedAt: {\n                    [Sequelize.Op.lte]: delayDate,\n                },\n            },\n            ...options,\n        });\n\n        return records.length;\n    }\n}\n\nexport default MissedParanetAssetRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/operation-id-repository.js",
    "content": "import Sequelize from 'sequelize';\n\nclass OperationIdRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.operation_ids;\n    }\n\n    async createOperationIdRecord(handlerData, options) {\n        return this.model.create(handlerData, options);\n    }\n\n    async getOperationIdRecord(operationId, options) {\n        return this.model.findOne({\n            where: {\n                operationId,\n            },\n            ...options,\n        });\n    }\n\n    async updateOperationIdRecord(data, operationId, options) {\n        await this.model.update(data, {\n            where: {\n                operationId,\n            },\n            ...options,\n        });\n    }\n\n    async removeOperationIdRecord(timeToBeDeleted, statuses, options) {\n        await this.model.destroy({\n            where: {\n                timestamp: { [Sequelize.Op.lt]: timeToBeDeleted },\n                status: { [Sequelize.Op.in]: statuses },\n            },\n            ...options,\n        });\n    }\n\n    async updateMinAcksReached(operationId, minAcksReached, options) {\n        await this.model.update(\n            { minAcksReached },\n            {\n                where: {\n                    operationId,\n                },\n                ...options,\n            },\n        );\n    }\n}\n\nexport default OperationIdRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/operation-repository.js",
    "content": "import { Sequelize } from 'sequelize';\n\nclass OperationRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.models = {\n            get: models.get,\n            publish: models.publish,\n            update: models.update,\n            publish_paranet: models.publish_paranet,\n            ask: models.ask,\n            finality: models.finality,\n            batch_get: models.batch_get,\n        };\n    }\n\n    async createOperationRecord(operation, operationId, status, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[operationModel].create(\n            {\n                operationId,\n                status,\n            },\n            options,\n        );\n    }\n\n    async findAndRemoveProcessedOperationRecords(operation, timestamp, limit, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n\n        return this.models[`${operationModel}`].destroy({\n            where: {\n                createdAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            limit,\n            ...options,\n        });\n    }\n\n    async removeOperationRecords(operation, ids, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[operationModel].destroy({\n            where: {\n                id: { [Sequelize.Op.in]: ids },\n            },\n            ...options,\n        });\n    }\n\n    async findProcessedOperations(operation, timestamp, limit, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[`${operationModel}`].findAll({\n            where: {\n                createdAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            order: [['createdAt', 'asc']],\n            raw: true,\n            limit,\n            ...options,\n        });\n    }\n\n    async getOperationStatus(operation, operationId, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[operationModel].findOne({\n            attributes: ['status'],\n            where: {\n                operationId,\n            },\n            ...options,\n        });\n    }\n\n    async updateOperationStatus(operation, operationId, status, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        await this.models[operationModel].update(\n            { status },\n            {\n                where: {\n                    operationId,\n                },\n                ...options,\n            },\n        );\n    }\n}\n\nexport default OperationRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/operation-response.js",
    "content": "import Sequelize from 'sequelize';\n\nclass OperationResponseRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.models = {\n            get_response: models.get_response,\n            publish_response: models.publish_response,\n            update_response: models.update_response,\n            publish_paranet_response: models.publish_paranet_response,\n            ask_response: models.ask_response,\n            finality_response: models.finality_response,\n        };\n    }\n\n    async createOperationResponseRecord(status, operation, operationId, message, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        await this.models[`${operationModel}_response`].create(\n            {\n                status,\n                message,\n                operationId,\n            },\n            options,\n        );\n    }\n\n    async getOperationResponsesStatuses(operation, operationId, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[`${operationModel}_response`].findAll({\n            attributes: ['status'],\n            where: {\n                operationId,\n            },\n            ...options,\n        });\n    }\n\n    async findProcessedOperationResponse(timestamp, limit, operation, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[`${operationModel}_response`].findAll({\n            where: {\n                createdAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            order: [['createdAt', 'asc']],\n            raw: true,\n            limit,\n            ...options,\n        });\n    }\n\n    async findAndRemoveProcessedOperationResponse(operation, timestamp, limit, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        return this.models[`${operationModel}_response`].destroy({\n            where: {\n                createdAt: { [Sequelize.Op.lte]: timestamp },\n            },\n            limit,\n            ...options,\n        });\n    }\n\n    async removeOperationResponse(ids, operation, options) {\n        const operationModel = operation.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();\n        await this.models[`${operationModel}_response`].destroy({\n            where: {\n                id: { [Sequelize.Op.in]: ids },\n            },\n            ...options,\n        });\n    }\n}\n\nexport default OperationResponseRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/paranet-kc-repository.js",
    "content": "import Sequelize from 'sequelize';\n\nclass ParanetKcRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.paranet_kc;\n    }\n\n    async createParanetKcRecords(paranetUal, blockchainId, uals, options = {}) {\n        return this.model.bulkCreate(\n            uals.map((ual) => ({ paranetUal, blockchainId, ual, isSynced: false })),\n            options,\n        );\n    }\n\n    async getCount(paranetUal, options = {}) {\n        return this.model.count({\n            where: {\n                paranetUal,\n            },\n            ...options,\n        });\n    }\n\n    async getCountSynced(paranetUal, options = {}) {\n        return this.model.count({\n            where: {\n                paranetUal,\n                isSynced: true,\n            },\n            ...options,\n        });\n    }\n\n    async getCountUnsynced(paranetUal, options = {}) {\n        return this.model.count({\n            where: {\n                paranetUal,\n                isSynced: false,\n            },\n            ...options,\n        });\n    }\n\n    async getSyncBatch(paranetUal, maxRetries, delayInMs, limit = null, options = {}) {\n        const queryOptions = {\n            where: {\n                paranetUal,\n                isSynced: false,\n                [Sequelize.Op.and]: [\n                    { retries: { [Sequelize.Op.lt]: maxRetries } },\n                    {\n                        [Sequelize.Op.or]: [\n                            { retries: 0 },\n                            {\n                                updatedAt: {\n                                    [Sequelize.Op.lte]: new Date(Date.now() - delayInMs),\n                                },\n                            },\n                        ],\n                    },\n                ],\n            },\n            order: [['retries', 'DESC']],\n            ...options,\n        };\n\n        if (limit !== null) {\n            queryOptions.limit = limit;\n        }\n\n        return this.model.findAll(queryOptions);\n    }\n\n    async incrementRetries(paranetUal, ual, errorMessage = null, options = {}) {\n        const [affectedRows] = await this.model.update(\n            {\n                retries: Sequelize.literal('retries + 1'),\n                errorMessage,\n            },\n            {\n                where: {\n                    ual,\n                    paranetUal,\n                },\n                ...options,\n            },\n        );\n\n        return affectedRows;\n    }\n\n    async markAsSynced(paranetUal, ual, options = {}) {\n        const [affectedRows] = await this.model.update(\n            { isSynced: true },\n            {\n                where: {\n                    ual,\n                    paranetUal,\n                },\n                ...options,\n            },\n        );\n\n        return affectedRows;\n    }\n}\n\nexport default ParanetKcRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/paranet-repository.js",
    "content": "import { Sequelize } from 'sequelize';\n\nclass ParanetRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.paranet;\n    }\n\n    async createParanetRecord(name, description, paranetId, blockchainId, options) {\n        return this.model.create(\n            {\n                name,\n                description,\n                paranetId,\n                kaCount: 0,\n                blockchainId,\n            },\n            {\n                ignoreDuplicates: true,\n                ...options,\n            },\n        );\n    }\n\n    async getParanet(paranetId, blockchainId, options) {\n        return this.model.findOne({\n            where: {\n                paranetId,\n                blockchainId,\n            },\n            ...options,\n        });\n    }\n\n    async addToParanetKaCount(paranetId, blockchainId, kaCount, options) {\n        return this.model.update(\n            {\n                kaCount: Sequelize.literal(`ka_count + ${kaCount}`),\n            },\n            {\n                where: {\n                    paranetId,\n                    blockchainId,\n                },\n                ...options,\n            },\n        );\n    }\n\n    async paranetExists(paranetId, blockchainId, options) {\n        const paranetRecord = await this.model.findOne({\n            where: {\n                paranetId,\n                blockchainId,\n            },\n            ...options,\n        });\n        return !!paranetRecord;\n    }\n\n    async getParanetKnowledgeAssetsCount(paranetId, blockchainId, options) {\n        return this.model.findAll({\n            attributes: ['ka_count'],\n            where: {\n                paranetId,\n                blockchainId,\n            },\n            ...options,\n        });\n    }\n\n    async incrementParanetKaCount(paranetId, blockchainId, options) {\n        return this.model.update(\n            {\n                kaCount: Sequelize.literal(`ka_count + 1`),\n            },\n            {\n                where: {\n                    paranetId,\n                    blockchainId,\n                },\n                ...options,\n            },\n        );\n    }\n\n    async getParanetsBlockchains(options) {\n        return this.model.findAll({\n            attributes: [\n                [Sequelize.fn('DISTINCT', Sequelize.col('blockchain_id')), 'blockchain_id'],\n            ],\n            ...options,\n        });\n    }\n}\n\nexport default ParanetRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/paranet-synced-asset-repository.js",
    "content": "// DEPRECATED\nclass ParanetSyncedAssetRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.paranet_synced_asset;\n    }\n\n    async createParanetSyncedAssetRecord(\n        blockchainId,\n        ual,\n        paranetUal,\n        publicAssertionId,\n        privateAssertionId,\n        sender,\n        transactionHash,\n        dataSource,\n        options,\n    ) {\n        return this.model.create(\n            {\n                blockchainId,\n                ual,\n                paranetUal,\n                publicAssertionId,\n                privateAssertionId,\n                sender,\n                transactionHash,\n                dataSource,\n            },\n            options,\n        );\n    }\n\n    async getParanetSyncedAssetRecordByUAL(ual, options) {\n        return this.model.findOne({\n            where: { ual },\n            ...options,\n        });\n    }\n\n    async getParanetSyncedAssetRecordsCountByDataSource(paranetUal, dataSource, options) {\n        return this.model.count({\n            where: {\n                paranetUal,\n                dataSource,\n            },\n            ...options,\n        });\n    }\n\n    async paranetSyncedAssetRecordExists(ual, options) {\n        const paranetSyncedAssetRecord = await this.getParanetSyncedAssetRecordByUAL(ual, options);\n\n        return !!paranetSyncedAssetRecord;\n    }\n}\n\nexport default ParanetSyncedAssetRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/random-sampling-challenge-repository.js",
    "content": "class RandomSamplingChallengeRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.random_sampling_challenge;\n    }\n\n    async createRandomSamplingChallengeRecord(randomSamplingChallenge, options) {\n        return this.model.create(randomSamplingChallenge, options);\n    }\n\n    async updateRandomSamplingChallengeRecord(randomSamplingChallenge, options) {\n        return this.model.update(randomSamplingChallenge, options);\n    }\n\n    async setCompletedAndScoreRandomSamplingChallengeRecord(\n        randomSamplingChallengeId,\n        completed,\n        score,\n        options,\n    ) {\n        return this.model.update(\n            { sentSuccessfully: completed, score },\n            { where: { id: randomSamplingChallengeId }, ...options },\n        );\n    }\n\n    async setCompletedAndFinalizedRandomSamplingChallengeRecord(\n        randomSamplingChallengeId,\n        completed,\n        finalized,\n        options,\n    ) {\n        return this.model.update(\n            { completed, finalized },\n            { where: { id: randomSamplingChallengeId }, ...options },\n        );\n    }\n\n    async getLatestRandomSamplingChallengeRecordForBlockchainId(blockchainId) {\n        return this.model.findOne({\n            where: {\n                blockchainId,\n            },\n            order: [['createdAt', 'DESC']],\n        });\n    }\n\n    async deleteRandomSamplingChallengeRecord(id, options = {}) {\n        return this.model.destroy({\n            where: { id },\n            ...options,\n        });\n    }\n\n    async deleteRandomSamplingChallengeForBlockchainIdEpoch(blockchainId, epoch) {\n        return this.model.destroy({\n            where: {\n                blockchainId,\n                epoch,\n            },\n        });\n    }\n}\n\nexport default RandomSamplingChallengeRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/shard-repository.js",
    "content": "import Sequelize from 'sequelize';\n\nclass ShardRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.shard;\n    }\n\n    async createManyPeerRecords(peerRecords, options) {\n        return this.model.bulkCreate(peerRecords, {\n            validate: true,\n            updateOnDuplicate: ['ask', 'stake', 'sha256'],\n            ...options,\n        });\n    }\n\n    async removeShardingTablePeerRecords(blockchainId, options) {\n        return this.model.destroy({\n            where: { blockchainId },\n            ...options,\n        });\n    }\n\n    async createPeerRecord(peerId, blockchainId, ask, stake, lastSeen, sha256, options) {\n        return this.model.create(\n            {\n                peerId,\n                blockchainId,\n                ask,\n                stake,\n                lastSeen,\n                sha256,\n            },\n            {\n                ignoreDuplicates: true,\n                ...options,\n            },\n        );\n    }\n\n    async getAllPeerRecords(blockchainId, filterInactive, options) {\n        const where = { blockchainId };\n\n        if (filterInactive) {\n            where.lastSeen = { [Sequelize.Op.eq]: Sequelize.col('last_dialed') };\n        }\n\n        return this.model.findAll({\n            where,\n            attributes: [\n                'peerId',\n                'blockchainId',\n                'ask',\n                'stake',\n                'lastSeen',\n                'lastDialed',\n                'sha256',\n            ],\n            order: [['sha256', 'asc']],\n            ...options,\n        });\n    }\n\n    async getPeerRecordsByIds(blockchainId, peerIds, options) {\n        return this.model.findAll({\n            where: {\n                blockchainId,\n                peerId: {\n                    [Sequelize.Op.in]: peerIds,\n                },\n            },\n            ...options,\n        });\n    }\n\n    async getPeerRecord(peerId, blockchainId, options) {\n        return this.model.findOne({\n            where: {\n                blockchainId,\n                peerId,\n            },\n            ...options,\n        });\n    }\n\n    async getPeersCount(blockchainId, options) {\n        return this.model.count({\n            where: {\n                blockchainId,\n            },\n            ...options,\n        });\n    }\n\n    async getPeersToDial(limit, dialFrequencyMillis, options) {\n        const result = await this.model.findAll({\n            attributes: ['peer_id'],\n            where: {\n                lastDialed: {\n                    [Sequelize.Op.lt]: new Date(Date.now() - dialFrequencyMillis),\n                },\n            },\n            order: [['last_dialed', 'asc']],\n            group: ['peer_id', 'last_dialed'],\n            limit,\n            raw: true,\n            ...options,\n        });\n        return (result ?? []).map((record) => ({ peerId: record.peer_id }));\n    }\n\n    async updatePeerAsk(peerId, blockchainId, ask, options) {\n        return this.model.update(\n            { ask },\n            {\n                where: {\n                    peerId,\n                    blockchainId,\n                },\n                ...options,\n            },\n        );\n    }\n\n    async updatePeerStake(peerId, blockchainId, stake, options) {\n        return this.model.update(\n            { stake },\n            {\n                where: {\n                    peerId,\n                    blockchainId,\n                },\n                ...options,\n            },\n        );\n    }\n\n    async updatePeerRecordLastDialed(peerId, timestamp, options) {\n        return this.model.update(\n            {\n                lastDialed: timestamp,\n            },\n            {\n                where: { peerId },\n                ...options,\n            },\n        );\n    }\n\n    async updatePeerRecordLastSeenAndLastDialed(peerId, timestamp, options) {\n        return this.model.update(\n            {\n                lastDialed: timestamp,\n                lastSeen: timestamp,\n            },\n            {\n                where: { peerId },\n                ...options,\n            },\n        );\n    }\n\n    async removePeerRecord(blockchainId, peerId, options) {\n        await this.model.destroy({\n            where: {\n                blockchainId,\n                peerId,\n            },\n            ...options,\n        });\n    }\n\n    async cleanShardingTable(blockchainId, options) {\n        await this.model.destroy({\n            where: blockchainId ? { blockchainId } : {},\n            ...options,\n        });\n    }\n\n    async isNodePartOfShard(blockchainId, peerId, options) {\n        const nodeIsPartOfShard = await this.model.findOne({\n            where: { blockchainId, peerId },\n            ...options,\n        });\n\n        return !!nodeIsPartOfShard;\n    }\n}\n\nexport default ShardRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/token-repository.js",
    "content": "import Sequelize from 'sequelize';\n\nclass TokenRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.token;\n    }\n\n    async saveToken(tokenId, userId, tokenName, expiresAt, options) {\n        return this.model.create(\n            {\n                id: tokenId,\n                userId,\n                expiresAt,\n                name: tokenName,\n            },\n            options,\n        );\n    }\n\n    async isTokenRevoked(tokenId, options) {\n        const token = await this.model.findByPk(tokenId, options);\n\n        return token && token.revoked;\n    }\n\n    async getTokenAbilities(tokenId, options) {\n        const abilities = await this.sequelize.query(\n            `SELECT a.name FROM token t\n                INNER JOIN user u ON t.user_id = u.id\n                INNER JOIN role r ON u.role_id = u.id\n                INNER JOIN role_ability ra on r.id = ra.role_id\n                INNER JOIN ability a on ra.ability_id = a.id\n                WHERE t.id=$tokenId;`,\n            {\n                bind: { tokenId },\n                type: Sequelize.QueryTypes.SELECT,\n                ...options,\n            },\n        );\n\n        return abilities.map((e) => e.name);\n    }\n}\n\nexport default TokenRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/repositories/user-repository.js",
    "content": "class UserRepository {\n    constructor(models) {\n        this.sequelize = models.sequelize;\n        this.model = models.user;\n    }\n\n    async getUser(username, options) {\n        return this.model.findOne({\n            where: {\n                name: username,\n            },\n            ...options,\n        });\n    }\n}\n\nexport default UserRepository;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/sequelize-migrator.js",
    "content": "import { createRequire } from 'module';\nimport { Umzug, SequelizeStorage } from 'umzug';\nimport { Sequelize } from 'sequelize';\nimport path from 'path';\n\nconst require = createRequire(import.meta.url);\n\nfunction createMigrator(sequelize, config, logger) {\n    return new Umzug({\n        migrations: {\n            glob: [\n                'migrations/*.{js,cjs,mjs}',\n                { cwd: path.dirname(import.meta.url.replace('file://', '')) },\n            ],\n            resolve: (params) => {\n                if (params.path.endsWith('.mjs') || params.path.endsWith('.js')) {\n                    const getModule = () => import(`file:///${params.path.replace(/\\\\/g, '/')}`);\n                    return {\n                        name: params.name,\n                        path: params.path,\n                        up: async (upParams) => (await getModule()).up(upParams, logger),\n                        down: async (downParams) => (await getModule()).down(downParams, logger),\n                    };\n                }\n                return {\n                    name: params.name,\n                    path: params.path,\n                    // eslint-disable-next-line import/no-dynamic-require\n                    ...require(params.path),\n                };\n            },\n        },\n        context: { queryInterface: sequelize.getQueryInterface(), Sequelize },\n        storage: new SequelizeStorage({ sequelize, tableName: 'sequelize_meta' }),\n        logger: config.logging ? config.logger : { info: () => {} },\n    });\n}\n\nexport default createMigrator;\n"
  },
  {
    "path": "src/modules/repository/implementation/sequelize/sequelize-repository.js",
    "content": "import mysql from 'mysql2';\nimport path from 'path';\nimport fs from 'fs';\nimport Sequelize from 'sequelize';\nimport { fileURLToPath } from 'url';\nimport createMigrator from './sequelize-migrator.js';\nimport BlockchainEventRepository from './repositories/blockchain-event-repository.js';\nimport BlockchainRepository from './repositories/blockchain-repository.js';\nimport CommandRepository from './repositories/command-repository.js';\nimport EventRepository from './repositories/event-repository.js';\nimport ParanetRepository from './repositories/paranet-repository.js';\nimport ParanetKcRepository from './repositories/paranet-kc-repository.js';\nimport OperationIdRepository from './repositories/operation-id-repository.js';\nimport OperationRepository from './repositories/operation-repository.js';\nimport OperationResponseRepository from './repositories/operation-response.js';\nimport ShardRepository from './repositories/shard-repository.js';\nimport TokenRepository from './repositories/token-repository.js';\nimport UserRepository from './repositories/user-repository.js';\n// import MissedParanetAssetRepository from './repositories/missed-paranet-asset-repository.js';\n// import ParanetSyncedAssetRepository from './repositories/paranet-synced-asset-repository.js';\nimport TriplesInsertCountRepository from './repositories/inserted-triples-repository.js';\nimport FinalityStatusRepository from './repositories/finality-status-repository.js';\nimport RandomSamplingChallengeRepository from './repositories/random-sampling-challenge-repository.js';\nimport LatestSyncedKcRepository from './repositories/latest-synced-kc-repository.js';\nimport BlockchainMissedKcRepository from './repositories/blockchain-missed-kc-repository.js';\n\nconst __dirname = fileURLToPath(new URL('.', import.meta.url));\n\nclass SequelizeRepository {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n\n        this.setEnvParameters();\n        await this.createDatabaseIfNotExists();\n        this.initializeSequelize();\n        await this.runMigrations();\n        await this.loadModels();\n\n        this.repositories = {\n            blockchain_event: new BlockchainEventRepository(this.models),\n            blockchain: new BlockchainRepository(this.models),\n            command: new CommandRepository(this.models),\n            event: new EventRepository(this.models),\n            paranet: new ParanetRepository(this.models),\n            // paranet_synced_asset: new ParanetSyncedAssetRepository(this.models),\n            // missed_paranet_asset: new MissedParanetAssetRepository(this.models),\n            paranet_kc: new ParanetKcRepository(this.models),\n            operation_id: new OperationIdRepository(this.models),\n            operation: new OperationRepository(this.models),\n            operation_response: new OperationResponseRepository(this.models),\n            shard: new ShardRepository(this.models),\n            token: new TokenRepository(this.models),\n            user: new UserRepository(this.models),\n            finality_status: new FinalityStatusRepository(this.models),\n            random_sampling_challenge: new RandomSamplingChallengeRepository(this.models),\n            inserted_triples: new TriplesInsertCountRepository(this.models),\n            latest_synced_kc: new LatestSyncedKcRepository(this.models),\n            blockchain_missed_kc: new BlockchainMissedKcRepository(this.models),\n        };\n    }\n\n    initializeSequelize() {\n        this.config.define = {\n            timestamps: false,\n            freezeTableName: true,\n        };\n        const sequelize = new Sequelize(\n            process.env.SEQUELIZE_REPOSITORY_DATABASE,\n            process.env.SEQUELIZE_REPOSITORY_USER,\n            process.env.SEQUELIZE_REPOSITORY_PASSWORD,\n            this.config,\n        );\n        this.models = { sequelize, Sequelize };\n    }\n\n    setEnvParameters() {\n        process.env.SEQUELIZE_REPOSITORY_USER = this.config.user;\n        process.env.SEQUELIZE_REPOSITORY_PASSWORD =\n            process.env.REPOSITORY_PASSWORD ?? this.config.password;\n        process.env.SEQUELIZE_REPOSITORY_DATABASE = this.config.database;\n        process.env.SEQUELIZE_REPOSITORY_HOST = this.config.host;\n        process.env.SEQUELIZE_REPOSITORY_PORT = this.config.port;\n        process.env.SEQUELIZE_REPOSITORY_DIALECT = this.config.dialect;\n    }\n\n    async createDatabaseIfNotExists() {\n        const connection = mysql.createConnection({\n            host: process.env.SEQUELIZE_REPOSITORY_HOST,\n            port: process.env.SEQUELIZE_REPOSITORY_PORT,\n            user: process.env.SEQUELIZE_REPOSITORY_USER,\n            password: process.env.SEQUELIZE_REPOSITORY_PASSWORD,\n        });\n        await connection\n            .promise()\n            .query(`CREATE DATABASE IF NOT EXISTS \\`${this.config.database}\\`;`);\n        connection.destroy();\n    }\n\n    async dropDatabase() {\n        const connection = mysql.createConnection({\n            host: process.env.SEQUELIZE_REPOSITORY_HOST,\n            port: process.env.SEQUELIZE_REPOSITORY_PORT,\n            user: process.env.SEQUELIZE_REPOSITORY_USER,\n            password: process.env.SEQUELIZE_REPOSITORY_PASSWORD,\n        });\n        await connection.promise().query(`DROP DATABASE IF EXISTS \\`${this.config.database}\\`;`);\n        connection.destroy();\n    }\n\n    async runMigrations() {\n        const migrator = createMigrator(this.models.sequelize, this.config, this.logger);\n        try {\n            await migrator.up();\n        } catch (error) {\n            this.logger.error(`Failed to execute ${migrator.name} migration: ${error.message}.`);\n            await migrator.down();\n            throw error;\n        }\n    }\n\n    async loadModels() {\n        const modelsDirectory = path.join(__dirname, 'models');\n        // disable automatic timestamps\n        const files = (await fs.promises.readdir(modelsDirectory)).filter(\n            (file) => file.indexOf('.') !== 0 && file.slice(-3) === '.js',\n        );\n        for (const file of files) {\n            // eslint-disable-next-line no-await-in-loop\n            const { default: f } = await import(`./models/${file}`);\n            const model = f(this.models.sequelize, Sequelize.DataTypes);\n            this.models[model.name] = model;\n        }\n\n        Object.keys(this.models).forEach((modelName) => {\n            if (this.models[modelName].associate) {\n                this.models[modelName].associate(this.models);\n            }\n        });\n    }\n\n    async transaction(execFn) {\n        if (execFn) {\n            return this.models.sequelize.transaction(async (t) => execFn(t));\n        }\n        return this.models.sequelize.transaction();\n    }\n\n    getRepository(repositoryName) {\n        return this.repositories[repositoryName];\n    }\n\n    async query(query, options) {\n        return this.models.sequelize.query(query, options);\n    }\n\n    async destroyAllRecords(table, options) {\n        return this.models[table].destroy({ where: {}, ...options });\n    }\n}\n\nexport default SequelizeRepository;\n"
  },
  {
    "path": "src/modules/repository/repository-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass RepositoryModuleManager extends BaseModuleManager {\n    getName() {\n        return 'repository';\n    }\n\n    getRepository(repoName) {\n        if (!this.initialized) {\n            throw new Error('RepositoryModuleManager not initialized');\n        }\n        return this.getImplementation().module.getRepository(repoName);\n    }\n\n    async transaction(execFn) {\n        if (this.initialized) {\n            return this.getImplementation().module.transaction(execFn);\n        }\n    }\n\n    async dropDatabase() {\n        if (this.initialized) {\n            return this.getImplementation().module.dropDatabase();\n        }\n    }\n\n    async query(query, options = {}) {\n        if (this.initialized) {\n            return this.getImplementation().module.query(query, options);\n        }\n    }\n\n    async destroyAllRecords(table, options = {}) {\n        if (this.initialized) {\n            return this.getImplementation().module.destroyAllRecords(table, options);\n        }\n    }\n\n    async updateCommand(update, options = {}) {\n        return this.getRepository('command').updateCommand(update, options);\n    }\n\n    async destroyCommand(name, options = {}) {\n        return this.getRepository('command').destroyCommand(name, options);\n    }\n\n    async createCommand(command, options = {}) {\n        return this.getRepository('command').createCommand(command, options);\n    }\n\n    async getCommandsWithStatus(statusArray, excludeNameArray = [], options = {}) {\n        return this.getRepository('command').getCommandsWithStatus(\n            statusArray,\n            excludeNameArray,\n            options,\n        );\n    }\n\n    async getCommandWithId(id, options = {}) {\n        return this.getRepository('command').getCommandWithId(id, options);\n    }\n\n    async removeCommands(ids, options = {}) {\n        return this.getRepository('command').removeCommands(ids, options);\n    }\n\n    async findFinalizedCommands(timestamp, limit, options = {}) {\n        return this.getRepository('command').findFinalizedCommands(timestamp, limit, options);\n    }\n\n    async findAndRemoveFinalizedCommands(timestamp, limit, options = {}) {\n        return this.getRepository('command').findAndRemoveFinalizedCommands(\n            timestamp,\n            limit,\n            options,\n        );\n    }\n\n    async findUnfinalizedCommandsByName(limit, options = {}) {\n        return this.getRepository('command').findUnfinalizedCommandsByName(limit, options);\n    }\n\n    async createOperationIdRecord(handlerData, options = {}) {\n        return this.getRepository('operation_id').createOperationIdRecord(handlerData, options);\n    }\n\n    async updateOperationIdRecord(data, operationId, options = {}) {\n        return this.getRepository('operation_id').updateOperationIdRecord(\n            data,\n            operationId,\n            options,\n        );\n    }\n\n    async getOperationIdRecord(operationId, options = {}) {\n        return this.getRepository('operation_id').getOperationIdRecord(operationId, options);\n    }\n\n    async removeOperationIdRecord(timeToBeDeleted, statuses, options = {}) {\n        return this.getRepository('operation_id').removeOperationIdRecord(\n            timeToBeDeleted,\n            statuses,\n            options,\n        );\n    }\n\n    async updateMinAcksReached(operationId, minAcksReached, options = {}) {\n        return this.getRepository('operation_id').updateMinAcksReached(\n            operationId,\n            minAcksReached,\n            options,\n        );\n    }\n\n    async createOperationRecord(operation, operationId, status, options = {}) {\n        return this.getRepository('operation').createOperationRecord(\n            operation,\n            operationId,\n            status,\n            options,\n        );\n    }\n\n    async removeOperationRecords(operation, ids, options = {}) {\n        return this.getRepository('operation').removeOperationRecords(operation, ids, options);\n    }\n\n    async findProcessedOperations(operation, timestamp, limit, options = {}) {\n        return this.getRepository('operation').findProcessedOperations(\n            operation,\n            timestamp,\n            limit,\n            options,\n        );\n    }\n\n    async findAndRemoveProcessedOperationRecords(operation, timestamp, limit, options = {}) {\n        return this.getRepository('operation').findAndRemoveProcessedOperationRecords(\n            operation,\n            timestamp,\n            limit,\n            options,\n        );\n    }\n\n    async getOperationStatus(operation, operationId, options = {}) {\n        return this.getRepository('operation').getOperationStatus(operation, operationId, options);\n    }\n\n    async updateOperationStatus(operation, operationId, status, options = {}) {\n        return this.getRepository('operation').updateOperationStatus(\n            operation,\n            operationId,\n            status,\n            options,\n        );\n    }\n\n    async createOperationResponseRecord(status, operation, operationId, errorMessage, options) {\n        return this.getRepository('operation_response').createOperationResponseRecord(\n            status,\n            operation,\n            operationId,\n            errorMessage,\n            options,\n        );\n    }\n\n    async getOperationResponsesStatuses(operation, operationId, options = {}) {\n        return this.getRepository('operation_response').getOperationResponsesStatuses(\n            operation,\n            operationId,\n            options,\n        );\n    }\n\n    async findProcessedOperationResponse(timestamp, limit, operation, options = {}) {\n        return this.getRepository('operation_response').findProcessedOperationResponse(\n            timestamp,\n            limit,\n            operation,\n            options,\n        );\n    }\n\n    async findAndRemoveProcessedOperationResponse(operation, timestamp, limit, options = {}) {\n        return this.getRepository('operation_response').findAndRemoveProcessedOperationResponse(\n            operation,\n            timestamp,\n            limit,\n            options,\n        );\n    }\n\n    async removeOperationResponse(ids, operation, options = {}) {\n        return this.getRepository('operation_response').removeOperationResponse(\n            ids,\n            operation,\n            options,\n        );\n    }\n\n    async createManyPeerRecords(peers, options = {}) {\n        return this.getRepository('shard').createManyPeerRecords(peers, options);\n    }\n\n    async removeShardingTablePeerRecords(blockchain, options = {}) {\n        return this.getRepository('shard').removeShardingTablePeerRecords(blockchain, options);\n    }\n\n    async createPeerRecord(peerId, blockchain, ask, stake, lastSeen, sha256, options = {}) {\n        return this.getRepository('shard').createPeerRecord(\n            peerId,\n            blockchain,\n            ask,\n            stake,\n            lastSeen,\n            sha256,\n            options,\n        );\n    }\n\n    async getPeerRecord(peerId, blockchain, options = {}) {\n        return this.getRepository('shard').getPeerRecord(peerId, blockchain, options);\n    }\n\n    async getAllPeerRecords(blockchain, filterInactive = false, options = {}) {\n        return this.getRepository('shard').getAllPeerRecords(blockchain, filterInactive, options);\n    }\n\n    async getPeerRecordsByIds(blockchain, peerIds, options = {}) {\n        return this.getRepository('shard').getPeerRecordsByIds(blockchain, peerIds, options);\n    }\n\n    async getPeersCount(blockchain, options = {}) {\n        return this.getRepository('shard').getPeersCount(blockchain, options);\n    }\n\n    async getPeersToDial(limit, dialFrequencyMillis, options = {}) {\n        return this.getRepository('shard').getPeersToDial(limit, dialFrequencyMillis, options);\n    }\n\n    async removePeerRecord(blockchain, peerId, options = {}) {\n        return this.getRepository('shard').removePeerRecord(blockchain, peerId, options);\n    }\n\n    async updatePeerRecordLastDialed(peerId, timestamp, options = {}) {\n        return this.getRepository('shard').updatePeerRecordLastDialed(peerId, timestamp, options);\n    }\n\n    async updatePeerRecordLastSeenAndLastDialed(peerId, timestamp, options = {}) {\n        return this.getRepository('shard').updatePeerRecordLastSeenAndLastDialed(\n            peerId,\n            timestamp,\n            options,\n        );\n    }\n\n    async updatePeerAsk(peerId, blockchainId, ask, options = {}) {\n        return this.getRepository('shard').updatePeerAsk(peerId, blockchainId, ask, options);\n    }\n\n    async updatePeerStake(peerId, blockchainId, stake, options = {}) {\n        return this.getRepository('shard').updatePeerStake(peerId, blockchainId, stake, options);\n    }\n\n    async getNeighbourhood(assertionId, r2, options = {}) {\n        return this.getRepository('shard').getNeighbourhood(assertionId, r2, options);\n    }\n\n    async cleanShardingTable(blockchainId, options = {}) {\n        return this.getRepository('shard').cleanShardingTable(blockchainId, options);\n    }\n\n    async isNodePartOfShard(blockchainId, peerId, options = {}) {\n        return this.getRepository('shard').isNodePartOfShard(blockchainId, peerId, options);\n    }\n\n    async destroyEvents(ids, options = {}) {\n        return this.getRepository('event').destroyEvents(ids, options);\n    }\n\n    async getUser(username, options = {}) {\n        return this.getRepository('user').getUser(username, options);\n    }\n\n    async saveToken(tokenId, userId, tokenName, expiresAt, options = {}) {\n        return this.getRepository('token').saveToken(\n            tokenId,\n            userId,\n            tokenName,\n            expiresAt,\n            options,\n        );\n    }\n\n    async isTokenRevoked(tokenId, options = {}) {\n        return this.getRepository('token').isTokenRevoked(tokenId, options);\n    }\n\n    async getTokenAbilities(tokenId, options = {}) {\n        return this.getRepository('token').getTokenAbilities(tokenId, options);\n    }\n\n    async insertBlockchainEvents(events, options = {}) {\n        return this.getRepository('blockchain_event').insertBlockchainEvents(events, options);\n    }\n\n    async getAllUnprocessedBlockchainEvents(blockchain, eventNames, options = {}) {\n        return this.getRepository('blockchain_event').getAllUnprocessedBlockchainEvents(\n            blockchain,\n            eventNames,\n            options,\n        );\n    }\n\n    async markAllBlockchainEventsAsProcessed(blockchain, options = {}) {\n        return this.getRepository('blockchain_event').markAllBlockchainEventsAsProcessed(\n            blockchain,\n            options,\n        );\n    }\n\n    async removeEvents(ids, options = {}) {\n        return this.getRepository('blockchain_event').removeEvents(ids, options);\n    }\n\n    async removeContractEventsAfterBlock(\n        blockchain,\n        contract,\n        contractAddress,\n        blockNumber,\n        transactionIndex,\n        options = {},\n    ) {\n        return this.getRepository('blockchain_event').removeContractEventsAfterBlock(\n            blockchain,\n            contract,\n            contractAddress,\n            blockNumber,\n            transactionIndex,\n            options,\n        );\n    }\n\n    async findProcessedEvents(timestamp, limit, options = {}) {\n        return this.getRepository('blockchain_event').findProcessedEvents(\n            timestamp,\n            limit,\n            options,\n        );\n    }\n\n    async findAndRemoveProcessedEvents(timestamp, limit, options = {}) {\n        return this.getRepository('blockchain_event').findAndRemoveProcessedEvents(\n            timestamp,\n            limit,\n            options,\n        );\n    }\n\n    async getLastCheckedBlock(blockchain, options = {}) {\n        return this.getRepository('blockchain').getLastCheckedBlock(blockchain, options);\n    }\n\n    async updateLastCheckedBlock(blockchain, currentBlock, timestamp, options = {}) {\n        return this.getRepository('blockchain').updateLastCheckedBlock(\n            blockchain,\n            currentBlock,\n            timestamp,\n            options,\n        );\n    }\n\n    async addToParanetKaCount(paranetId, blockchainId, kaCount, options = {}) {\n        return this.getRepository('paranet').addToParanetKaCount(\n            paranetId,\n            blockchainId,\n            kaCount,\n            options,\n        );\n    }\n\n    async createParanetRecord(name, description, paranetId, blockchainId, options = {}) {\n        this.getRepository('paranet').createParanetRecord(\n            name,\n            description,\n            paranetId,\n            blockchainId,\n            options,\n        );\n    }\n\n    async paranetExists(paranetId, blockchainId, options = {}) {\n        return this.getRepository('paranet').paranetExists(paranetId, blockchainId, options);\n    }\n\n    async getParanet(paranetId, blockchainId, options = {}) {\n        return this.getRepository('paranet').getParanet(paranetId, blockchainId, options);\n    }\n\n    async getParanetKnowledgeAssetsCount(paranetId, blockchainId, options = {}) {\n        return this.getRepository('paranet').getParanetKnowledgeAssetsCount(\n            paranetId,\n            blockchainId,\n            options,\n        );\n    }\n\n    async getParanetsBlockchains(options = {}) {\n        return this.getRepository('paranet').getParanetsBlockchains(options);\n    }\n\n    async incrementParanetKaCount(paranetId, blockchainId, options = {}) {\n        return this.getRepository('paranet').incrementParanetKaCount(\n            paranetId,\n            blockchainId,\n            options,\n        );\n    }\n\n    async createParanetKcRecords(paranetUal, blockchainId, uals, options = {}) {\n        return this.getRepository('paranet_kc').createParanetKcRecords(\n            paranetUal,\n            blockchainId,\n            uals,\n            options,\n        );\n    }\n\n    async getParanetKcCount(paranetUal, options = {}) {\n        return this.getRepository('paranet_kc').getCount(paranetUal, options);\n    }\n\n    async getParanetKcSyncedCount(paranetUal, options = {}) {\n        return this.getRepository('paranet_kc').getCountSynced(paranetUal, options);\n    }\n\n    async getParanetKcUnsyncedCount(paranetUal, options = {}) {\n        return this.getRepository('paranet_kc').getCountUnsynced(paranetUal, options);\n    }\n\n    async getParanetKcSyncBatch(paranetUal, retriesMax, retryDelayMs, limit = null, options = {}) {\n        return this.getRepository('paranet_kc').getSyncBatch(\n            paranetUal,\n            retriesMax,\n            retryDelayMs,\n            limit,\n            options,\n        );\n    }\n\n    async paranetKcIncrementRetries(paranetUal, ual, errorMessage = null, options = {}) {\n        return this.getRepository('paranet_kc').incrementRetries(\n            paranetUal,\n            ual,\n            errorMessage,\n            options,\n        );\n    }\n\n    async paranetKcMarkAsSynced(paranetUal, ual, options = {}) {\n        return this.getRepository('paranet_kc').markAsSynced(paranetUal, ual, options);\n    }\n\n    async getFinalityAcksCount(ual, options = {}) {\n        return this.getRepository('finality_status').getFinalityAcksCount(ual, options);\n    }\n\n    async getPublishOperationIdByUal(ual, options = {}) {\n        return this.getRepository('finality_status').getPublishOperationIdByUal(ual, options);\n    }\n\n    async getLatestRandomSamplingChallengeRecordForBlockchainId(blockchainId, limit = 1) {\n        return this.getRepository(\n            'random_sampling_challenge',\n        ).getLatestRandomSamplingChallengeRecordForBlockchainId(blockchainId, limit);\n    }\n\n    async createRandomSamplingChallengeRecord(randomSamplingChallenge, options) {\n        return this.getRepository('random_sampling_challenge').createRandomSamplingChallengeRecord(\n            randomSamplingChallenge,\n            options,\n        );\n    }\n\n    async updateRandomSamplingChallengeRecord(randomSamplingChallenge, options) {\n        return this.getRepository('random_sampling_challenge').updateRandomSamplingChallengeRecord(\n            randomSamplingChallenge,\n            options,\n        );\n    }\n\n    async deleteRandomSamplingChallengeRecord(randomSamplingChallengeId, options) {\n        return this.getRepository('random_sampling_challenge').deleteRandomSamplingChallengeRecord(\n            randomSamplingChallengeId,\n            options,\n        );\n    }\n\n    async setCompletedAndScoreRandomSamplingChallengeRecord(\n        randomSamplingChallengeId,\n        completed,\n        score,\n        options,\n    ) {\n        return this.getRepository(\n            'random_sampling_challenge',\n        ).setCompletedAndScoreRandomSamplingChallengeRecord(\n            randomSamplingChallengeId,\n            completed,\n            score,\n            options,\n        );\n    }\n\n    async setCompletedAndFinalizedRandomSamplingChallengeRecord(\n        randomSamplingChallengeId,\n        completed,\n        finalized,\n        options,\n    ) {\n        return this.getRepository(\n            'random_sampling_challenge',\n        ).setCompletedAndFinalizedRandomSamplingChallengeRecord(\n            randomSamplingChallengeId,\n            completed,\n            finalized,\n            options,\n        );\n    }\n\n    async saveFinalityAck(publishOperationId, ual, peerId, options = {}) {\n        return this.getRepository('finality_status').saveFinalityAck(\n            publishOperationId,\n            ual,\n            peerId,\n            options,\n        );\n    }\n\n    async incrementInsertedTriples(count) {\n        return this.getRepository('inserted_triples').increment(count);\n    }\n\n    async getKCStorageContracts(blockchainId) {\n        return this.getRepository('latest_synced_kc').getKCStorageContracts(blockchainId);\n    }\n\n    async getSyncRecordForBlockchain(blockchainId) {\n        return this.getRepository('latest_synced_kc').getSyncRecordForBlockchain(blockchainId);\n    }\n\n    async addSyncContracts(blockchainId, contracts) {\n        return this.getRepository('latest_synced_kc').addSyncContracts(blockchainId, contracts);\n    }\n\n    async insertMissedKc(blockchainId, records, error, options = {}) {\n        return this.getRepository('blockchain_missed_kc').insertMissedKc(\n            blockchainId,\n            records,\n            error,\n            options,\n        );\n    }\n\n    async getMissedKcForRetry(blockchain, contractAddress, limit, options) {\n        return this.getRepository('blockchain_missed_kc').getMissedKcForRetry(\n            blockchain,\n            contractAddress,\n            limit,\n            options,\n        );\n    }\n\n    async updateLatestSyncedKc(blockchainId, contractAddress, latestSyncedKc, options = {}) {\n        return this.getRepository('latest_synced_kc').updateLatestSyncedKc(\n            blockchainId,\n            contractAddress,\n            latestSyncedKc,\n            options,\n        );\n    }\n\n    async incrementRetryCount(blockchain, records, options) {\n        return this.getRepository('blockchain_missed_kc').incrementRetryCount(\n            blockchain,\n            records,\n            options,\n        );\n    }\n\n    async setSyncedToTrue(blockchain, records, options) {\n        return this.getRepository('blockchain_missed_kc').setSyncedToTrue(\n            blockchain,\n            records,\n            options,\n        );\n    }\n\n    async getMissedKcForRetryCount(blockchain, contractAddress, options) {\n        return this.getRepository('blockchain_missed_kc').getMissedKcForRetryCount(\n            blockchain,\n            contractAddress,\n            options,\n        );\n    }\n}\n\nexport default RepositoryModuleManager;\n"
  },
  {
    "path": "src/modules/telemetry/implementation/quest-telemetry.js",
    "content": "import { Sender } from '@questdb/nodejs-client';\n\nclass QuestTelemetry {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n        this.localSender = Sender.fromConfig(this.config.localEndpoint);\n        if (this.config.sendToSignalingService) {\n            this.signalingServiceSender = Sender.fromConfig(this.config.signalingServiceEndpoint);\n        }\n    }\n\n    listenOnEvents(eventEmitter, onEventReceived) {\n        return eventEmitter.on('operation_status_changed', onEventReceived);\n    }\n\n    async sendTelemetryData(\n        operationId,\n        timestamp,\n        blockchainId = '',\n        name = '',\n        value1 = null,\n        value2 = null,\n        value3 = null,\n    ) {\n        try {\n            const table = this.localSender.table('event');\n\n            table.symbol('operationId', operationId || 'NULL');\n            table.symbol('blockchainId', blockchainId || 'NULL');\n            table.symbol('name', name || 'NULL');\n            if (value1 !== null) table.symbol('value1', value1);\n            if (value2 !== null) table.symbol('value2', value2);\n            if (value3 !== null) table.symbol('value3', value3);\n            table.timestampColumn('timestamp', timestamp * 1000);\n\n            await table.at(Date.now(), 'ms');\n            await this.localSender.flush();\n            await this.localSender.close();\n\n            // this.logger.info('Event telemetry successfully sent to local QuestDB');\n        } catch (err) {\n            this.logger.error(`Error sending telemetry to local QuestDB: ${err.message}`);\n        }\n    }\n}\n\nexport default QuestTelemetry;\n"
  },
  {
    "path": "src/modules/telemetry/telemetry-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass TelemetryModuleManager extends BaseModuleManager {\n    constructor(ctx) {\n        super(ctx);\n        this.eventEmitter = ctx.eventEmitter;\n    }\n\n    getName() {\n        return 'telemetry';\n    }\n\n    async initialize() {\n        await super.initialize();\n\n        this.listenOnEvents((eventData) => {\n            this.sendTelemetryData(\n                eventData.operationId,\n                eventData.timestamp,\n                eventData.blockchainId,\n                eventData.lastEvent,\n                eventData.value1,\n                eventData.value2,\n                eventData.value3,\n            );\n        });\n    }\n\n    listenOnEvents(onEventReceived) {\n        if (this.config.modules.telemetry.enabled && this.initialized) {\n            return this.getImplementation().module.listenOnEvents(\n                this.eventEmitter,\n                onEventReceived,\n            );\n        }\n    }\n\n    async sendTelemetryData(operationId, timestamp, blockchainId, name, value1, value2, value3) {\n        if (this.config.modules.telemetry.enabled && this.initialized) {\n            return this.getImplementation().module.sendTelemetryData(\n                operationId,\n                timestamp,\n                blockchainId,\n                name,\n                value1,\n                value2,\n                value3,\n            );\n        }\n    }\n}\n\nexport default TelemetryModuleManager;\n"
  },
  {
    "path": "src/modules/triple-store/implementation/ot-blazegraph/ot-blazegraph.js",
    "content": "import axios from 'axios';\nimport OtTripleStore from '../ot-triple-store.js';\nimport { MEDIA_TYPES } from '../../../../constants/constants.js';\n\nclass OtBlazegraph extends OtTripleStore {\n    async initialize(config, logger) {\n        await super.initialize(config, logger);\n        // this regex will match \\Uxxxxxxxx but will exclude cases where there is a double slash before U (\\\\U)\n        this.unicodeRegex = /(?<!\\\\)\\\\U([a-fA-F0-9]{8})/g;\n\n        await Promise.all(\n            Object.keys(this.repositories).map(async (repository) => {\n                await this.createRepository(repository);\n            }),\n        );\n    }\n\n    async createRepository(repository) {\n        const { url, name } = this.repositories[repository];\n        if (!(await this.repositoryExists(repository))) {\n            await axios.post(\n                `${url}/blazegraph/namespace`,\n                `com.bigdata.rdf.sail.truthMaintenance=false\\n` +\n                    `com.bigdata.namespace.${name}.spo.com.bigdata.btree.BTree.branchingFactor=1024\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.textIndex=false\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.justify=false\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.statementIdentifiers=false\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.axiomsClass=com.bigdata.rdf.axioms.NoAxioms\\n` +\n                    `com.bigdata.rdf.sail.namespace=${name}\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.quads=true\\n` +\n                    `com.bigdata.namespace.${name}.lex.com.bigdata.btree.BTree.branchingFactor=400\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.geoSpatial=false\\n` +\n                    `com.bigdata.journal.Journal.groupCommit=false\\n` +\n                    `com.bigdata.rdf.sail.isolatableIndices=false\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.enableRawRecordsSupport=false\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.Options.inlineTextLiterals=true\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.Options.maxInlineTextLength=128\\n` +\n                    `com.bigdata.rdf.store.AbstractTripleStore.Options.blobsThreshold=256\\n`,\n                {\n                    headers: {\n                        'Content-Type': 'text/plain',\n                    },\n                },\n            );\n        }\n    }\n\n    initializeSparqlEndpoints(repository) {\n        const { url, name } = this.repositories[repository];\n        this.repositories[repository].sparqlEndpoint = `${url}/blazegraph/namespace/${name}/sparql`;\n        this.repositories[\n            repository\n        ].sparqlEndpointUpdate = `${url}/blazegraph/namespace/${name}/sparql`;\n    }\n\n    getRepositoryUrl(repository) {\n        return this.repositories[repository].url;\n    }\n\n    hasUnicodeCodePoints(input) {\n        return this.unicodeRegex.test(input);\n    }\n\n    decodeUnicodeCodePoints(input) {\n        const decodedString = input.replace(this.unicodeRegex, (match, hex) => {\n            const codePoint = parseInt(hex, 16);\n            return String.fromCodePoint(codePoint);\n        });\n\n        return decodedString;\n    }\n\n    utfConverter(input) {\n        return Buffer.from(input, 'utf8').toString();\n    }\n\n    async construct(repository, query, timeout) {\n        return this._executeQuery(repository, query, MEDIA_TYPES.N_QUADS, timeout);\n    }\n\n    async select(repository, query, timeout) {\n        const result = await this._executeQuery(repository, query, MEDIA_TYPES.JSON, timeout);\n        return result ? JSON.parse(result) : [];\n    }\n\n    async ask(repository, query, timeout = 10000) {\n        const result = await this._executeQuery(repository, query, MEDIA_TYPES.JSON, timeout);\n        return result ? JSON.parse(result).boolean : false;\n    }\n\n    async _executeQuery(repository, query, mediaType, timeout) {\n        const result = await axios.post(this.repositories[repository].sparqlEndpoint, query, {\n            headers: {\n                'Content-Type': 'application/sparql-query',\n                'X-BIGDATA-MAX-QUERY-MILLIS': timeout,\n                Accept: mediaType,\n            },\n        });\n        let response;\n        if (mediaType === MEDIA_TYPES.JSON) {\n            // Check if this is an ASK query by looking for the boolean property\n            if (result.data.boolean !== undefined) {\n                // This is an ASK query response\n                response = JSON.stringify(result.data);\n            } else {\n                // This is a SELECT query response\n                const { bindings } = result.data.results;\n\n                let output = '[\\n';\n\n                bindings.forEach((binding, bindingIndex) => {\n                    let string = '  {\\n';\n\n                    const keys = Object.keys(binding);\n\n                    keys.forEach((key, index) => {\n                        let value = '';\n                        const entry = binding[key];\n\n                        if (entry.datatype) {\n                            // e.g., \"\\\"6900000\\\"^^http://www.w3.org/2001/XMLSchema#integer\"\n                            const literal = `\"${entry.value}\"^^${entry.datatype}`;\n                            value = JSON.stringify(literal);\n                        } else if (entry['xml:lang']) {\n                            // e.g., \"\\\"text here\\\"@en\"\n                            const literal = `\"${entry.value}\"@${entry['xml:lang']}`;\n                            value = JSON.stringify(literal);\n                        } else if (entry.type === 'uri') {\n                            // URIs should be escaped and quoted directly\n                            value = JSON.stringify(entry.value);\n                        } else {\n                            // For plain literals, wrap in quotes and stringify\n                            const literal = `\"${entry.value}\"`;\n                            value = JSON.stringify(literal);\n                        }\n\n                        const isLast = index === keys.length - 1;\n                        string += `    \"${key}\": ${value}${isLast ? '' : ','}\\n`;\n                    });\n\n                    const isLastBinding = bindingIndex === bindings.length - 1;\n                    string += `  }${isLastBinding ? '\\n' : ',\\n'}`;\n\n                    output += string;\n                });\n\n                output += ']';\n                response = output;\n            }\n        } else {\n            response = result.data;\n        }\n\n        // Handle Blazegraph special characters corruption\n        if (this.hasUnicodeCodePoints(response)) {\n            response = this.decodeUnicodeCodePoints(response);\n        }\n\n        response = this.utfConverter(response);\n\n        return response;\n    }\n\n    async healthCheck(repository) {\n        try {\n            const response = await axios.get(\n                `${this.repositories[repository].url}/blazegraph/status`,\n                {},\n            );\n            if (response.data !== null) {\n                return true;\n            }\n            return false;\n        } catch (e) {\n            return false;\n        }\n    }\n\n    async queryVoid(repository, query, timeout) {\n        try {\n            return await axios.post(this.repositories[repository].sparqlEndpoint, query, {\n                headers: {\n                    'Content-Type': 'application/sparql-update; charset=UTF-8',\n                    'X-BIGDATA-MAX-QUERY-MILLIS': timeout,\n                },\n            });\n        } catch (error) {\n            const status = error?.response?.status;\n            const dataSnippet =\n                typeof error?.response?.data === 'string' ? error.response.data.slice(0, 200) : '';\n            this.logger.error(\n                `[OtBlazegraph.queryVoid] Update failed for ${repository} (status: ${status}): ${\n                    error.message\n                }${dataSnippet ? ` | data: ${dataSnippet}` : ''}`,\n            );\n            throw error;\n        }\n    }\n\n    async deleteRepository(repository) {\n        const { url, name } = this.repositories[repository];\n        this.logger.info(\n            `Deleting ${this.getName()} triple store repository: ${repository} with name: ${name}`,\n        );\n\n        if (await this.repositoryExists(repository)) {\n            await axios\n                .delete(`${url}/blazegraph/namespace/${name}`, {})\n                .catch((e) =>\n                    this.logger.error(\n                        `Error while deleting ${this.getName()} triple store repository: ${repository} with name: ${name}. Error: ${\n                            e.message\n                        }`,\n                    ),\n                );\n        }\n    }\n\n    async repositoryExists(repository) {\n        const { url, name } = this.repositories[repository];\n\n        try {\n            await axios.get(`${url}/blazegraph/namespace/${name}/properties`, {\n                params: {\n                    'describe-each-named-graph': 'false',\n                },\n                headers: {\n                    Accept: 'application/ld+json',\n                },\n            });\n            return true;\n        } catch (error) {\n            if (error.response && error.response.status === 404) {\n                // Expected error: GraphDB is up but has not created node0 repository\n                // dkg-engine will create repo in initialization\n                return false;\n            }\n            this.logger.error(\n                `Error while getting ${this.getName()} repositories. Error: ${error.message}`,\n            );\n\n            return false;\n        }\n    }\n\n    getName() {\n        return 'OtBlazegraph';\n    }\n}\n\nexport default OtBlazegraph;\n"
  },
  {
    "path": "src/modules/triple-store/implementation/ot-fuseki/ot-fuseki.js",
    "content": "import axios from 'axios';\nimport OtTripleStore from '../ot-triple-store.js';\n\nclass OtFuseki extends OtTripleStore {\n    async initialize(config, logger) {\n        await super.initialize(config, logger);\n        await Promise.all(\n            Object.keys(this.repositories).map(async (repository) => {\n                await this.createRepository(repository);\n            }),\n        );\n    }\n\n    async createRepository(repository) {\n        const { url, name } = this.repositories[repository];\n\n        if (!(await this.repositoryExists(repository))) {\n            await axios.post(\n                `${url}/$/datasets?dbName=${name}&dbType=tdb`,\n                {},\n                {\n                    headers: {\n                        'Content-Type': 'text/plain',\n                    },\n                },\n            );\n        }\n    }\n\n    initializeSparqlEndpoints(repository) {\n        const { url, name } = this.repositories[repository];\n        this.repositories[repository].sparqlEndpoint = `${url}/${name}/sparql`;\n        this.repositories[repository].sparqlEndpointUpdate = `${url}/${name}/update`;\n    }\n\n    async healthCheck(repository) {\n        try {\n            const response = await axios.get(`${this.repositories[repository].url}/$/ping`, {});\n            if (response.data !== null) {\n                return true;\n            }\n            return false;\n        } catch (e) {\n            return false;\n        }\n    }\n\n    async deleteRepository(repository) {\n        const { url, name } = this.repositories[repository];\n        this.logger.info(\n            `Deleting ${this.getName()} triple store repository: ${repository} with name: ${name}`,\n        );\n\n        if (await this.repositoryExists(repository)) {\n            await axios\n                .delete(`${url}/$/datasets/${name}`, {})\n                .catch((e) =>\n                    this.logger.error(\n                        `Error while deleting ${this.getName()} triple store repository: ${repository} with name: ${name}. Error: ${\n                            e.message\n                        }`,\n                    ),\n                );\n        }\n    }\n\n    async repositoryExists(repository) {\n        const { url, name } = this.repositories[repository];\n        try {\n            const response = await axios.get(`${url}/$/datasets`);\n\n            return response.data.datasets.filter((dataset) => dataset['ds.name'] === `/${name}`)\n                .length;\n        } catch (error) {\n            this.logger.error(\n                `Error while getting ${this.getName()} repositories. Error: ${error.message}`,\n            );\n\n            return false;\n        }\n    }\n\n    getName() {\n        return 'OtFuseki';\n    }\n}\n\nexport default OtFuseki;\n"
  },
  {
    "path": "src/modules/triple-store/implementation/ot-graphdb/ot-graphdb.js",
    "content": "import graphdb from 'graphdb';\nimport axios from 'axios';\nimport OtTripleStore from '../ot-triple-store.js';\n\nconst { server, repository: repo, http } = graphdb;\n\nclass OtGraphdb extends OtTripleStore {\n    async initialize(config, logger) {\n        await super.initialize(config, logger);\n\n        await Promise.all(\n            Object.keys(this.repositories).map(async (repository) => {\n                await this.createRepository(repository);\n            }),\n        );\n    }\n\n    async createRepository(repository) {\n        const { url, name } = this.repositories[repository];\n        const serverConfig = new server.ServerClientConfig(url)\n            .setTimeout(40000)\n            .setHeaders({\n                Accept: http.RDFMimeType.N_QUADS,\n            })\n            .setKeepAlive(true);\n        const s = new server.GraphDBServerClient(serverConfig);\n        // eslint-disable-next-line no-await-in-loop\n        const exists = await s.hasRepository(name);\n        if (!exists) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                await s.createRepository(\n                    new repo.RepositoryConfig(\n                        name,\n                        '',\n                        new Map(),\n                        '',\n                        'Repo title',\n                        repo.RepositoryType.FREE,\n                    ),\n                );\n            } catch (e) {\n                // eslint-disable-next-line no-await-in-loop\n                await s.createRepository(\n                    new repo.RepositoryConfig(\n                        name,\n                        '',\n                        {},\n                        'graphdb:SailRepository',\n                        'Repo title',\n                        'graphdb',\n                    ),\n                );\n            }\n        }\n    }\n\n    initializeSparqlEndpoints(repository) {\n        const { url, name } = this.repositories[repository];\n        this.repositories[repository].sparqlEndpoint = `${url}/repositories/${name}`;\n        this.repositories[\n            repository\n        ].sparqlEndpointUpdate = `${url}/repositories/${name}/statements`;\n    }\n\n    async healthCheck(repository) {\n        const { url, username, password } = this.repositories[repository];\n        try {\n            const response = await axios.get(\n                `${url}/repositories/${repository}/health`,\n                {},\n                {\n                    auth: {\n                        username,\n                        password,\n                    },\n                },\n            );\n            if (response.data.status === 'green') {\n                return true;\n            }\n            return false;\n        } catch (e) {\n            if (e.response && e.response.status === 404) {\n                // Expected error: GraphDB is up but has not created node0 repository\n                // dkg-engine will create repo in initialization\n                return true;\n            }\n            return false;\n        }\n    }\n\n    async deleteRepository(repository) {\n        const { url, name } = this.repositories[repository];\n        this.logger.info(\n            `Deleting ${this.getName()} triple store repository: ${repository} with name: ${name}`,\n        );\n\n        const serverConfig = new server.ServerClientConfig(url)\n            .setTimeout(40000)\n            .setHeaders({\n                Accept: http.RDFMimeType.N_QUADS,\n            })\n            .setKeepAlive(true);\n        const s = new server.GraphDBServerClient(serverConfig);\n        s.deleteRepository(name).catch((e) =>\n            this.logger.warn(\n                `Error while deleting ${this.getName()} triple store repository: ${repository} with name: ${name}. Error: ${\n                    e.message\n                }`,\n            ),\n        );\n    }\n\n    async repositoryExists(repository) {\n        const { url, name } = this.repositories[repository];\n\n        const serverConfig = new server.ServerClientConfig(url)\n            .setTimeout(40000)\n            .setHeaders({\n                Accept: http.RDFMimeType.N_QUADS,\n            })\n            .setKeepAlive(true);\n        const s = new server.GraphDBServerClient(serverConfig);\n\n        return s.hasRepository(name);\n    }\n\n    getName() {\n        return 'GraphDB';\n    }\n\n    async queryVoid(repository, query) {\n        const endpoint = `${this.repositories[repository].url}/repositories/${repository}/statements`;\n\n        try {\n            await axios.post(endpoint, query, {\n                headers: {\n                    'Content-Type': 'application/sparql-update',\n                },\n            });\n\n            return true;\n        } catch (error) {\n            console.error(`SPARQL update failed: ${error.message}`);\n            throw error;\n        }\n    }\n\n    async ask(repository, query) {\n        const endpoint = `${this.repositories[repository].url}/repositories/${repository}`;\n\n        try {\n            const response = await axios.post(endpoint, query, {\n                headers: {\n                    'Content-Type': 'application/sparql-query',\n                    Accept: 'application/json',\n                },\n            });\n\n            return response.data.boolean; // true or false\n        } catch (error) {\n            console.error(`ASK query failed: ${error.message}`);\n            throw error;\n        }\n    }\n\n    async construct(repository, query, accept = 'application/n-triples') {\n        const endpoint = `${this.repositories[repository].url}/repositories/${repository}`;\n        try {\n            const response = await axios.post(endpoint, query, {\n                headers: {\n                    'Content-Type': 'application/sparql-query',\n                    Accept: accept,\n                },\n            });\n            return response.data;\n        } catch (error) {\n            console.error(`SPARQL query failed: ${error.message}`);\n            throw error;\n        }\n    }\n\n    async select(repository, query) {\n        // todo: add media type once bug is fixed\n        // no media type is passed because of comunica bug\n        // https://github.com/comunica/comunica/issues/1034\n        const result = await this._executeQuery(repository, query);\n        return result ?? [];\n    }\n\n    async _executeQuery(repository, query, mediaType, accept = 'application/sparql-results+json') {\n        const endpoint = `${this.repositories[repository].url}/repositories/${repository}`;\n        try {\n            const response = await axios.post(endpoint, query, {\n                headers: {\n                    'Content-Type': 'application/sparql-query',\n                    Accept: accept,\n                },\n            });\n            const result = [];\n            for (const elem of response.data.results.bindings) {\n                const obj = {};\n                Object.keys(elem).forEach((key) => {\n                    obj[key] = elem[key].value;\n                });\n                result.push(obj);\n            }\n            return result;\n        } catch (error) {\n            console.error(`SPARQL query failed: ${error.message}`);\n            throw error;\n        }\n    }\n}\n\nexport default OtGraphdb;\n"
  },
  {
    "path": "src/modules/triple-store/implementation/ot-neptune/ot-neptune.js",
    "content": "import axios from 'axios';\nimport OtTripleStore from '../ot-triple-store.js';\n\nclass OtNeptune extends OtTripleStore {\n    async initialize(config, logger) {\n        await super.initialize(config, logger);\n    }\n\n    /* eslint-disable-next-line no-unused-vars */\n    async createRepository(repository) {\n        /* eslint-disable-next-line no-empty-function */\n    }\n\n    initializeSparqlEndpoints(repository) {\n        /* eslint-disable-next-line no-unused-vars */\n        const { url, name } = this.repositories[repository];\n        this.repositories[repository].sparqlEndpoint = `${url}/sparql`;\n        this.repositories[repository].sparqlEndpointUpdate = `${url}/sparql`;\n    }\n\n    /* eslint-disable-next-line no-unused-vars */\n    async deleteRepository(repository) {\n        /* eslint-disable-next-line no-empty-function */\n    }\n\n    async healthCheck(repository) {\n        try {\n            const response = await axios.get(`${this.repositories[repository].url}/status`);\n            if (response.data && response.data.status === 'healthy') {\n                return true;\n            }\n            return false;\n        } catch (e) {\n            return false;\n        }\n    }\n\n    getName() {\n        return 'OtNeptune';\n    }\n}\nexport default OtNeptune;\n"
  },
  {
    "path": "src/modules/triple-store/implementation/ot-triple-store.js",
    "content": "import { QueryEngine as Engine } from '@comunica/query-sparql';\nimport axios from 'axios';\nimport { setTimeout } from 'timers/promises';\nimport {\n    SCHEMA_CONTEXT,\n    TRIPLE_STORE_CONNECT_MAX_RETRIES,\n    TRIPLE_STORE_CONNECT_RETRY_FREQUENCY,\n    MEDIA_TYPES,\n    UAL_PREDICATE,\n    BASE_NAMED_GRAPHS,\n    TRIPLE_ANNOTATION_LABEL_PREDICATE,\n    TRIPLES_VISIBILITY,\n    DKG_PREDICATE,\n    HAS_KNOWLEDGE_ASSET_SUFFIX,\n    HAS_NAMED_GRAPH_SUFFIX,\n    DKG_METADATA_PREDICATES,\n} from '../../../constants/constants.js';\n\nclass OtTripleStore {\n    async initialize(config, logger) {\n        this.logger = logger;\n        this.repositories = config.repositories;\n        this.initializeRepositories();\n        this.initializeContexts();\n        await this.ensureConnections();\n        this.queryEngine = new Engine();\n    }\n\n    initializeRepositories() {\n        for (const repository of Object.keys(this.repositories)) {\n            this.initializeSparqlEndpoints(repository);\n        }\n    }\n\n    async initializeParanetRepository(repository) {\n        const publicCurrent = 'publicCurrent';\n        this.repositories[repository] = {\n            url: this.repositories[publicCurrent].url,\n            name: repository,\n            username: this.repositories[publicCurrent].username,\n            password: this.repositories[publicCurrent].password,\n        };\n        this.initializeSparqlEndpoints(repository);\n        this.initializeContexts();\n        await this.ensureConnections();\n        await this.createRepository(repository);\n    }\n\n    repositoryInitilized(repository) {\n        return Boolean(this.repositories && this.repositories[repository]);\n    }\n\n    async createRepository() {\n        throw Error('CreateRepository not implemented');\n    }\n\n    initializeSparqlEndpoints() {\n        throw Error('initializeSparqlEndpoints not implemented');\n    }\n\n    async deleteRepository() {\n        throw Error('deleteRepository not implemented');\n    }\n\n    initializeContexts() {\n        for (const repository in this.repositories) {\n            const sources = [\n                {\n                    type: 'sparql',\n                    value: this.repositories[repository].sparqlEndpoint,\n                },\n            ];\n\n            this.repositories[repository].updateContext = {\n                sources,\n                destination: {\n                    type: 'sparql',\n                    value: this.repositories[repository].sparqlEndpointUpdate,\n                },\n                httpTimeout: 60_000,\n                httpBodyTimeout: true,\n            };\n            this.repositories[repository].queryContext = {\n                sources,\n                httpTimeout: 60_000,\n                httpBodyTimeout: true,\n            };\n        }\n    }\n\n    async ensureConnections() {\n        const ensureConnectionPromises = Object.keys(this.repositories).map(async (repository) => {\n            let ready = await this.healthCheck(repository);\n            let retries = 0;\n            while (!ready && retries < TRIPLE_STORE_CONNECT_MAX_RETRIES) {\n                retries += 1;\n                this.logger.warn(\n                    `Cannot connect to Triple store (${this.getName()}), repository: ${repository}, located at: ${\n                        this.repositories[repository].url\n                    }  retry number: ${retries}/${TRIPLE_STORE_CONNECT_MAX_RETRIES}. Retrying in ${TRIPLE_STORE_CONNECT_RETRY_FREQUENCY} seconds.`,\n                );\n                /* eslint-disable no-await-in-loop */\n                await setTimeout(TRIPLE_STORE_CONNECT_RETRY_FREQUENCY * 1000);\n                ready = await this.healthCheck(repository);\n            }\n            if (retries === TRIPLE_STORE_CONNECT_MAX_RETRIES) {\n                this.logger.error(\n                    `Triple Store (${this.getName()})  not available, max retries reached.`,\n                );\n                process.exit(1);\n            }\n        });\n\n        await Promise.all(ensureConnectionPromises);\n    }\n\n    async insertAssertionBatch(\n        repository,\n        insertMap,\n        metadata,\n        createdMetadata,\n        currentNamedGraphTriples,\n        timeout,\n    ) {\n        const graphsForDataInsert = [];\n        for (const [ual, triples] of Object.entries(insertMap)) {\n            const graph = `\n                GRAPH <${ual}> {\n                    ${triples.join('\\n')}\n                }\n            `;\n            graphsForDataInsert.push(graph);\n        }\n\n        const metadataGraphForInsert = `\n            GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                ${Object.values(metadata)\n                    .map((triples) => triples.join('\\n'))\n                    .join('\\n')}\n                ${createdMetadata.join('\\n')}\n            }\n        `;\n\n        const currentNamedGraphInsert = `\n            GRAPH <${BASE_NAMED_GRAPHS.CURRENT}> {\n                ${currentNamedGraphTriples.join('\\n')}\n            }\n        `;\n\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            INSERT DATA {\n                ${graphsForDataInsert.join('\\n')}\n                ${metadataGraphForInsert}\n                ${currentNamedGraphInsert}\n            }\n        `;\n\n        await this.queryVoid(repository, query, timeout);\n    }\n\n    async deleteUniqueKnowledgeCollectionTriplesFromUnifiedGraph(repository, namedGraph, ual) {\n        const query = `\n            DELETE {\n                GRAPH <${namedGraph}> {\n                    ?s ?p ?o .\n                    << ?s ?p ?o >> ?annotationPredicate ?annotationValue .\n                }\n            }\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} ?annotationValue .\n                }\n                FILTER(STRSTARTS(STR(?annotationValue), \"${ual}/\"))\n\n                {\n                    SELECT ?s ?p ?o (COUNT(?annotationValue) AS ?annotationCount)\n                    WHERE {\n                        GRAPH <${namedGraph}> {\n                            << ?s ?p ?o >> ${UAL_PREDICATE} ?annotationValue .\n                        }\n                    }\n                    GROUP BY ?s ?p ?o\n                    HAVING(?annotationCount = 1)\n                }\n            }\n        `;\n\n        await this.queryVoid(repository, query);\n    }\n\n    async getKnowledgeCollectionFromUnifiedGraph(repository, namedGraph, ual, sort) {\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            CONSTRUCT { ?s ?p ?o . }\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} ?ual .\n                    FILTER(STRSTARTS(STR(?ual), \"${ual}/\"))\n                }\n            }\n            ${sort ? 'ORDER BY ?s' : ''}\n        `;\n\n        return this.construct(repository, query);\n    }\n\n    async getKnowledgeCollectionPublicFromUnifiedGraph(repository, namedGraph, ual, sort) {\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            CONSTRUCT { ?s ?p ?o }\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} ?ual .\n                    FILTER(STRSTARTS(STR(?ual), \"${ual}/\"))\n                    FILTER NOT EXISTS {\n                        << ?s ?p ?o >> ${TRIPLE_ANNOTATION_LABEL_PREDICATE} \"private\" .\n                    }\n                }\n            }\n            ${sort ? 'ORDER BY ?s' : ''}\n        `;\n\n        return this.construct(repository, query);\n    }\n\n    async knowledgeCollectionExistsInUnifiedGraph(repository, namedGraph, ual) {\n        const query = `\n            ASK\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} ?ual\n                    FILTER(STRSTARTS(STR(?ual), \"${ual}/\"))\n                }\n            }\n        `;\n\n        return this.ask(repository, query);\n    }\n\n    async deleteUniqueKnowledgeAssetTriplesFromUnifiedGraph(repository, namedGraph, ual) {\n        const query = `\n            DELETE {\n                GRAPH <${namedGraph}> {\n                    ?s ?p ?o .\n                    << ?s ?p ?o >> ?annotationPredicate ?annotationValue .\n                }\n            }\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} <${ual}> .\n                }\n\n                {\n                    SELECT ?s ?p ?o (COUNT(?annotationValue) AS ?annotationCount)\n                    WHERE {\n                        GRAPH <${namedGraph}> {\n                            << ?s ?p ?o >> ${UAL_PREDICATE} ?annotationValue .\n                        }\n                    }\n                    GROUP BY ?s ?p ?o\n                    HAVING(?annotationCount = 1)\n                }\n            }\n        `;\n\n        await this.queryVoid(repository, query);\n    }\n\n    async getKnowledgeAssetFromUnifiedGraph(repository, namedGraph, ual) {\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            CONSTRUCT { ?s ?p ?o . }\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} <${ual}> .\n                }\n            }\n        `;\n\n        return this.construct(repository, query);\n    }\n\n    async getKnowledgeAssetPublicFromUnifiedGraph(repository, namedGraph, ual) {\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            CONSTRUCT { ?s ?p ?o }\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} <${ual}> .\n                    FILTER NOT EXISTS {\n                        << ?s ?p ?o >> ${TRIPLE_ANNOTATION_LABEL_PREDICATE} \"private\" .\n                    }\n                }\n            }\n        `;\n\n        return this.construct(repository, query);\n    }\n\n    async knowledgeAssetExistsInUnifiedGraph(repository, namedGraph, ual) {\n        const query = `\n            ASK\n            WHERE {\n                GRAPH <${namedGraph}> {\n                    << ?s ?p ?o >> ${UAL_PREDICATE} <${ual}>\n                }\n            }\n        `;\n\n        return this.ask(repository, query);\n    }\n\n    async createKnowledgeCollectionNamedGraphs(\n        repository,\n        uals,\n        assetsNQuads,\n        visibility,\n        timeout,\n        retries = 5,\n        retryDelay = 10,\n    ) {\n        const graphInserts = uals\n            .map(\n                (ual, index) => `\n                GRAPH <${ual}/${visibility}> {\n                    ${assetsNQuads[index].join('\\n')}\n                }\n            `,\n            )\n            .join('\\n');\n\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            INSERT DATA {\n                ${graphInserts}\n            }\n        `;\n\n        let attempts = 0;\n        let success = false;\n\n        while (attempts < retries && !success) {\n            try {\n                await this.queryVoid(repository, query, timeout);\n                success = true;\n            } catch (error) {\n                attempts += 1;\n                if (attempts <= retries) {\n                    this.logger.warn(\n                        `Batch insert failed for ${uals[0]\n                            .split('/')\n                            .slice(0, -1)\n                            .join(\n                                '/',\n                            )} graphs. Attempt ${attempts}/${retries}. Retrying in ${retryDelay}ms.`,\n                    );\n                    await setTimeout(retryDelay);\n                } else {\n                    throw new Error(\n                        `Failed to perform batch insert after ${retries} attempts. Error: ${error.message}`,\n                    );\n                }\n            }\n        }\n    }\n\n    async createParanetKnoledgeCollectionConnection(\n        repository,\n        kcUAL,\n        paranetUAL,\n        contentType,\n        timeout,\n    ) {\n        const getNamedGraphsQuery = `\n            PREFIX dkg: <https://ontology.origintrail.io/dkg/1.0#>\n            SELECT ?g WHERE {\n                GRAPH <metadata:graph> {\n                    <${kcUAL}> dkg:hasNamedGraph ?g .\n                }\n            }\n        `;\n\n        let metadataConnections = await this.select(repository, getNamedGraphsQuery);\n\n        if (contentType === 'public') {\n            metadataConnections = metadataConnections.filter((row) => !row.g.includes('/private'));\n        }\n\n        const paranetConnectionTriples = metadataConnections\n            .map(\n                (row) =>\n                    ` <${paranetUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${row.g}> .`,\n            )\n            .join('\\n');\n\n        const query = `\n        INSERT DATA {\n            GRAPH <${paranetUAL}> {\n                   ${paranetConnectionTriples}\n            }\n        }\n        `;\n\n        await this.queryVoid(repository, query, timeout);\n    }\n\n    async insertMetadataTriples(repository, kcUAL, kaUALs, visibility, timeout) {\n        const currentTriples = kaUALs\n            .map(\n                (ual) =>\n                    `<current:graph> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${ual}/${visibility}> .`,\n            )\n            .join('\\n');\n\n        const connectionTriples = kaUALs\n            .map((ual) => {\n                const graphWithVisibility = `${ual}/${visibility}`;\n                return [\n                    `<${kcUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`,\n                    `<${kcUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`,\n                ].join('\\n');\n            })\n            .join('\\n');\n\n        const query = `\n            INSERT DATA {\n                GRAPH <${BASE_NAMED_GRAPHS.CURRENT}> {\n                    ${currentTriples}\n                }\n\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                    ${connectionTriples}\n                }\n            }\n        `;\n\n        await this.queryVoid(repository, query, timeout);\n    }\n\n    async deleteKnowledgeCollectionNamedGraphs(repository, namedGraphs) {\n        if (!namedGraphs || namedGraphs.length === 0) return;\n\n        const query = `${namedGraphs.map((graph) => `DROP GRAPH <${graph}>`).join(';\\n')};`;\n\n        await this.queryVoid(repository, query);\n    }\n\n    async getKnowledgeCollectionNamedGraphsOld(repository, ual, tokenIds, visibility, timeout) {\n        const namedGraphs = Array.from(\n            { length: tokenIds.endTokenId - tokenIds.startTokenId + 1 },\n            (_, i) => tokenIds.startTokenId + i,\n        )\n            .filter((id) => !tokenIds.burned.includes(id))\n            .map((id) => `${ual}/${id}`);\n\n        const assertion = {};\n        if (visibility === TRIPLES_VISIBILITY.PUBLIC || visibility === TRIPLES_VISIBILITY.ALL) {\n            const query = `\n            PREFIX schema: <http://schema.org/>\n            CONSTRUCT {\n                ?s ?p ?o .\n              }\n              WHERE {\n                GRAPH ?g {\n                  ?s ?p ?o .\n                }\n                VALUES ?g {\n                    ${namedGraphs\n                        .map((graph) => `<${graph}/${TRIPLES_VISIBILITY.PUBLIC}>`)\n                        .join('\\n')}\n                }\n              }`;\n            assertion.public = await this.construct(repository, query, timeout);\n        }\n        if (visibility === TRIPLES_VISIBILITY.PRIVATE || visibility === TRIPLES_VISIBILITY.ALL) {\n            const query = `\n            PREFIX schema: <http://schema.org/>\n            CONSTRUCT {\n                ?s ?p ?o .\n              }\n              WHERE {\n                GRAPH ?g {\n                  ?s ?p ?o .\n                }\n                VALUES ?g {\n                    ${namedGraphs\n                        .map((graph) => `<${graph}/${TRIPLES_VISIBILITY.PRIVATE}>`)\n                        .join('\\n')}\n                }\n              }`;\n            assertion.private = await this.construct(repository, query, timeout);\n        }\n\n        return assertion;\n    }\n\n    async getKnowledgeCollectionNamedGraphsOldInBatch(\n        repository,\n        ualTokenIds,\n        visibility,\n        timeout,\n    ) {\n        const kaUALs = Array.from(Object.entries(ualTokenIds)).flatMap(([ual, tokenIds]) => {\n            const arr = Array.from(\n                { length: tokenIds.endTokenId - tokenIds.startTokenId + 1 },\n                (_, i) => tokenIds.startTokenId + i,\n            );\n            if (\n                visibility === TRIPLES_VISIBILITY.PUBLIC ||\n                visibility === TRIPLES_VISIBILITY.PRIVATE\n            ) {\n                return arr\n                    .filter((id) => !tokenIds.burned.includes(id))\n                    .map((id) => `<${ual}/${id}/${visibility}>`);\n            }\n            // visibility === TRIPLES_VISIBILITY.ALL;\n            // It should add both public and private suffixes\n            return arr\n                .filter((id) => !tokenIds.burned.includes(id))\n                .flatMap((id) => [\n                    `<${ual}/${id}/${TRIPLES_VISIBILITY.PUBLIC}>`,\n                    `<${ual}/${id}/${TRIPLES_VISIBILITY.PRIVATE}>`,\n                ]);\n        });\n\n        const query = `\n            SELECT ?g ?s ?p ?o\n            WHERE {\n                VALUES ?g {\n                    ${kaUALs.join('\\n')}\n                }\n                GRAPH ?g {\n                    ?s ?p ?o\n                }\n            }\n        `;\n\n        const result = await axios.post(this.repositories[repository].sparqlEndpoint, query, {\n            headers: {\n                'Content-Type': 'application/sparql-query',\n                Accept: 'text/tab-separated-values',\n                'X-BIGDATA-MAX-QUERY-MILLIS': timeout,\n            },\n        });\n        return result.data;\n    }\n\n    async checkIfKnowledgeAssetExists(repository, kaUAL, timeout = 10000) {\n        const query = `\n            ASK {\n                GRAPH <${kaUAL}> {\n                    ?s ?p ?o\n                }\n            }`;\n        try {\n            return this.ask(repository, query, timeout);\n        } catch (error) {\n            this.logger.error(`Error checking if knowledge asset exists: ${error}`);\n            return false;\n        }\n    }\n\n    async getKnowledgeCollectionNamedGraphs(\n        repository,\n        ual,\n        knowledgeAssetId,\n        visibility,\n        timeout,\n    ) {\n        const assertion = {};\n        let publicPrivateMetadataConnections = null;\n\n        const getNamedGraphsQuery = `\n            PREFIX dkg: <https://ontology.origintrail.io/dkg/1.0#>\n            SELECT ?g WHERE {\n                GRAPH <metadata:graph> {\n                    <${ual}> dkg:hasNamedGraph ?g .\n                }\n            }\n        `;\n\n        const getConstructQuery = (graphList) => `\n            PREFIX schema: <http://schema.org/>\n            CONSTRUCT {\n                ?s ?p ?o .\n            }\n            WHERE {\n                GRAPH ?g {\n                    ?s ?p ?o .\n                }\n                VALUES ?g {\n                    ${graphList.map((g) => `<${g}>`).join('\\n')}\n                }\n            }\n        `;\n\n        const buildSingleGraph = async (visibilityType) => {\n            const graph = `${ual}/${knowledgeAssetId}/${visibilityType}`;\n            return getConstructQuery([graph]);\n        };\n\n        const buildAllGraphs = async (filter) => {\n            if (!publicPrivateMetadataConnections) {\n                publicPrivateMetadataConnections = await this.select(\n                    repository,\n                    getNamedGraphsQuery,\n                    timeout,\n                );\n            }\n            return publicPrivateMetadataConnections\n                .map((row) => row.g)\n                .filter((graph) => graph.includes(filter));\n        };\n\n        if (visibility === TRIPLES_VISIBILITY.PUBLIC || visibility === TRIPLES_VISIBILITY.ALL) {\n            if (knowledgeAssetId) {\n                const singleGraph = await buildSingleGraph(TRIPLES_VISIBILITY.PUBLIC);\n                assertion.public = await this.construct(repository, singleGraph, timeout);\n            } else {\n                const publicGraphs = await buildAllGraphs('/public');\n                assertion.public = publicGraphs.length\n                    ? await this.construct(repository, getConstructQuery(publicGraphs), timeout)\n                    : '';\n            }\n        }\n\n        if (visibility === TRIPLES_VISIBILITY.PRIVATE || visibility === TRIPLES_VISIBILITY.ALL) {\n            if (knowledgeAssetId) {\n                const singleGraph = await buildSingleGraph(TRIPLES_VISIBILITY.PRIVATE);\n                assertion.private = await this.construct(repository, singleGraph, timeout);\n            } else {\n                const privateGraphs = await buildAllGraphs('/private');\n                assertion.private = privateGraphs.length\n                    ? await this.construct(repository, getConstructQuery(privateGraphs), timeout)\n                    : '';\n            }\n        }\n\n        return assertion;\n    }\n\n    async getMetadataInBatch(repository, uals) {\n        const query = `\n            CONSTRUCT {\n                ?ual ?p ?o\n            }\n            WHERE {\n                VALUES ?ual {\n                    ${uals.map((ual) => `<${ual}>`).join('\\n')}\n                }\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                    ?ual ?p ?o\n                }\n            }\n        `;\n\n        return this.construct(repository, query);\n    }\n\n    async knowledgeCollectionNamedGraphsExist(repository, ual) {\n        const query = `\n        ASK {\n            GRAPH <${ual}/1/public> {\n                ?s ?p ?o\n            }\n        }\n    `;\n\n        return this.ask(repository, query);\n    }\n\n    async deleteKnowledgeAssetNamedGraph(repository, ual) {\n        const query = `\n            DROP GRAPH <${ual}>\n        `;\n\n        await this.queryVoid(repository, query);\n    }\n\n    async getKnowledgeAssetNamedGraph(repository, ual, visibility, timeout) {\n        let whereClause;\n        const nquads = {};\n        switch (visibility) {\n            case TRIPLES_VISIBILITY.PUBLIC:\n            case TRIPLES_VISIBILITY.PRIVATE: {\n                whereClause = `\n                    WHERE {\n                        GRAPH <${ual}/${visibility}> {\n                            ?s ?p ?o .\n                        }\n                    }\n                `;\n                const query = `\n                    PREFIX schema: <${SCHEMA_CONTEXT}>\n                    CONSTRUCT { ?s ?p ?o }\n                    ${whereClause}\n                `;\n                nquads[visibility] = await this.construct(repository, query, timeout);\n                nquads[visibility] = nquads[visibility].split('\\n');\n                break;\n            }\n            case TRIPLES_VISIBILITY.ALL: {\n                const publicWhereClause = `\n                    WHERE {\n                        GRAPH <${ual}/${TRIPLES_VISIBILITY.PUBLIC}> {\n                            ?s ?p ?o .\n                        }\n                    }\n                `;\n                const privateWhereClause = `\n                    WHERE {\n                        GRAPH <${ual}/${TRIPLES_VISIBILITY.PRIVATE}> {\n                            ?s ?p ?o .\n                        }\n                    }\n                `;\n                const publicQuery = `\n                    PREFIX schema: <${SCHEMA_CONTEXT}>\n                    CONSTRUCT { ?s ?p ?o }\n                    ${publicWhereClause}\n                `;\n                const privateQuery = `\n                    PREFIX schema: <${SCHEMA_CONTEXT}>\n                    CONSTRUCT { ?s ?p ?o }\n                    ${privateWhereClause}\n                `;\n                nquads[TRIPLES_VISIBILITY.PUBLIC] = await this.construct(\n                    repository,\n                    publicQuery,\n                    timeout,\n                );\n                nquads[TRIPLES_VISIBILITY.PRIVATE] = await this.construct(\n                    repository,\n                    privateQuery,\n                    timeout,\n                );\n                nquads[TRIPLES_VISIBILITY.PUBLIC] = nquads[TRIPLES_VISIBILITY.PUBLIC]\n                    .split('\\n')\n                    .slice(0, -1);\n\n                nquads[TRIPLES_VISIBILITY.PRIVATE] = nquads[TRIPLES_VISIBILITY.PRIVATE]\n                    .split('\\n')\n                    .slice(0, -1);\n                break;\n            }\n            default:\n                throw new Error(`Unsupported visibility: ${visibility}`);\n        }\n        return nquads;\n    }\n\n    async knowledgeAssetNamedGraphExists(repository, name) {\n        const query = `\n            ASK {\n                GRAPH <${name}> {\n                    ?s ?p ?o\n                }\n            }\n        `;\n\n        return this.ask(repository, query);\n    }\n\n    async insertKnowledgeCollectionMetadata(repository, metadataNQuads, timeout) {\n        const query = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            INSERT DATA {\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> { \n                    ${metadataNQuads} \n                } \n            }\n        `;\n\n        await this.queryVoid(repository, query, timeout);\n    }\n\n    async deleteKnowledgeCollectionMetadata(repository, uals) {\n        const cleanedUals = [...new Set(uals.map((ual) => ual.replace(/\\/(public|private)$/, '')))];\n        const kcUAL = cleanedUals[0].split('/').slice(0, -1).join('/');\n\n        let query = `${cleanedUals\n            .map(\n                (ual) =>\n                    `DELETE WHERE { GRAPH <${BASE_NAMED_GRAPHS.METADATA}> { <${ual}> ?p ?o . } }`,\n            )\n            .join(';\\n')};`;\n\n        query += `DELETE WHERE { GRAPH <${BASE_NAMED_GRAPHS.METADATA}> { <${kcUAL}> ?p ?o . } }`;\n\n        await this.queryVoid(repository, query);\n    }\n\n    async deletePublishTimestampMetadata(repository, ual) {\n        const query = `\n            DELETE WHERE {\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                    <${ual}> <${DKG_METADATA_PREDICATES.PUBLISH_TIME}> ?o .\n                }\n            }\n        `;\n\n        await this.queryVoid(repository, query);\n    }\n\n    async getKnowledgeCollectionMetadata(repository, ual, timeout) {\n        const query = `\n        CONSTRUCT {\n            <${ual}> ?p ?o .\n        }\n        WHERE {\n            GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                <${ual}> ?p ?o .\n            }\n        }\n    `;\n\n        return this.construct(repository, query, timeout);\n    }\n\n    async getKnowledgeAssetMetadata(repository, ual, timeout) {\n        const query = `\n            CONSTRUCT { <${ual}> ?p ?o . }\n            WHERE {\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                    <${ual}> ?p ?o .\n                }\n            }\n        `;\n\n        return this.construct(repository, query, timeout);\n    }\n\n    async knowledgeCollectionMetadataExists(repository, ual) {\n        const query = `\n            ASK {\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                    ?ual ?p ?o\n                    FILTER(STRSTARTS(STR(?ual), \"${ual}/\"))\n                }\n            }\n        `;\n\n        return this.ask(repository, query);\n    }\n\n    async findAllNamedGraphsByUAL(repository, ual) {\n        const query = `\n            SELECT DISTINCT ?g\n            WHERE {\n                GRAPH ?g {\n                    ?s ?p ?o\n                }\n                FILTER(STRSTARTS(STR(?g), \"${ual}\"))\n            }`;\n\n        this.select(repository, query);\n    }\n\n    async findAllSubjectsWithGraphNames(repository, ual) {\n        const query = `\n            SELECT DISTINCT ?s ?g\n            WHERE {\n                GRAPH ?g {\n                    ?s ?p ?o\n                }\n                FILTER(STRSTARTS(STR(?g), \"${ual}\"))\n            }`;\n        this.select(repository, query);\n    }\n\n    async getLatestAssertionId(repository, ual) {\n        const query = `SELECT ?assertionId\n        WHERE {\n          GRAPH <assets:graph> {\n            <${ual}> ?p ?assertionId\n          }\n        }`;\n\n        const data = await this.select(repository, query);\n\n        const fullAssertionId = data?.[0]?.assertionId;\n\n        const latestAssertionId = fullAssertionId?.replace('assertion:', '');\n\n        return latestAssertionId;\n    }\n\n    async construct(repository, query) {\n        return this._executeQuery(repository, query, MEDIA_TYPES.N_QUADS);\n    }\n\n    async select(repository, query) {\n        // todo: add media type once bug is fixed\n        // no media type is passed because of comunica bug\n        // https://github.com/comunica/comunica/issues/1034\n        const result = await this._executeQuery(repository, query);\n        return result ? JSON.parse(result) : [];\n    }\n\n    async queryVoid(repository, query) {\n        return this.queryEngine.queryVoid(query, this.repositories[repository].updateContext);\n    }\n\n    async ask(repository, query) {\n        return this.queryEngine.queryBoolean(query, this.repositories[repository].queryContext);\n    }\n\n    async healthCheck() {\n        return true;\n    }\n\n    async _executeQuery(repository, query, mediaType) {\n        const result = await this.queryEngine.query(\n            query,\n            this.repositories[repository].queryContext,\n        );\n        const { data } = await this.queryEngine.resultToString(result, mediaType);\n\n        let response = '';\n\n        for await (const chunk of data) {\n            response += chunk;\n        }\n\n        return response;\n    }\n\n    async reinitialize() {\n        const ready = await this.healthCheck();\n        if (!ready) {\n            this.logger.warn(\n                `Cannot connect to Triple store (${this.getName()}), check if your triple store is running.`,\n            );\n        } else {\n            this.implementation.initialize(this.logger);\n        }\n    }\n\n    // OLD REPOSITORIES SUPPORT\n\n    cleanEscapeCharacter(query) {\n        return query.replace(/['|[\\]\\\\]/g, '\\\\$&');\n    }\n\n    async getV6Assertion(repository, assertionId) {\n        if (!assertionId) return '';\n\n        const escapedGraphName = this.cleanEscapeCharacter(assertionId);\n\n        const query = `PREFIX schema: <${SCHEMA_CONTEXT}>\n                    CONSTRUCT { ?s ?p ?o }\n                    WHERE {\n                        {\n                            GRAPH <assertion:${escapedGraphName}>\n                            {\n                                ?s ?p ?o .\n                            }\n                        }\n                    }`;\n        return this.construct(repository, query);\n    }\n}\n\nexport default OtTripleStore;\n"
  },
  {
    "path": "src/modules/triple-store/triple-store-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass TripleStoreModuleManager extends BaseModuleManager {\n    initializeParanetRepository(repository) {\n        return this.getImplementation().module.initializeParanetRepository(repository);\n    }\n\n    repositoryInitilized(repository) {\n        return this.getImplementation().module.repositoryInitilized(repository);\n    }\n\n    getRepositoryUrl(implementationName, repository) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getRepositoryUrl(repository);\n        }\n    }\n\n    async deleteUniqueKnowledgeCollectionTriplesFromUnifiedGraph(\n        implementationName,\n        repository,\n        namedGraph,\n        ual,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.deleteUniqueKnowledgeCollectionTriplesFromUnifiedGraph(\n                repository,\n                namedGraph,\n                ual,\n            );\n        }\n    }\n\n    async getKnowledgeCollectionFromUnifiedGraph(\n        implementationName,\n        repository,\n        namedGraph,\n        ual,\n        sort,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeCollectionFromUnifiedGraph(repository, namedGraph, ual, sort);\n        }\n    }\n\n    async getKnowledgeCollectionPublicFromUnifiedGraph(\n        implementationName,\n        repository,\n        namedGraph,\n        ual,\n        sort,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeCollectionPublicFromUnifiedGraph(\n                repository,\n                namedGraph,\n                ual,\n                sort,\n            );\n        }\n    }\n\n    async knowledgeCollectionExistsInUnifiedGraph(implementationName, repository, namedGraph, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.knowledgeCollectionExistsInUnifiedGraph(repository, namedGraph, ual);\n        }\n    }\n\n    async deleteUniqueKnowledgeAssetTriplesFromUnifiedGraph(\n        implementationName,\n        repository,\n        namedGraph,\n        ual,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.deleteUniqueKnowledgeAssetTriplesFromUnifiedGraph(repository, namedGraph, ual);\n        }\n    }\n\n    async getKnowledgeAssetFromUnifiedGraph(implementationName, repository, namedGraph, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeAssetFromUnifiedGraph(repository, namedGraph, ual);\n        }\n    }\n\n    async getKnowledgeAssetPublicFromUnifiedGraph(implementationName, repository, namedGraph, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeAssetPublicFromUnifiedGraph(repository, namedGraph, ual);\n        }\n    }\n\n    async knowledgeAssetExistsInUnifiedGraph(implementationName, repository, namedGraph, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.knowledgeAssetExistsInUnifiedGraph(repository, namedGraph, ual);\n        }\n    }\n\n    async createKnowledgeCollectionNamedGraphs(\n        implementationName,\n        repository,\n        uals,\n        assetsNQuads,\n        visibility,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.createKnowledgeCollectionNamedGraphs(\n                repository,\n                uals,\n                assetsNQuads,\n                visibility,\n                timeout,\n            );\n        }\n    }\n\n    async createParanetKnoledgeCollectionConnection(\n        implementationName,\n        repository,\n        knowledgeCollectionUal,\n        paranetUAL,\n        contentType,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.createParanetKnoledgeCollectionConnection(\n                repository,\n                knowledgeCollectionUal,\n                paranetUAL,\n                contentType,\n                timeout,\n            );\n        }\n    }\n\n    async insertMetadataTriples(implementationName, repository, kcUal, uals, visibility, timeout) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.insertMetadataTriples(\n                repository,\n                kcUal,\n                uals,\n                visibility,\n                timeout,\n            );\n        }\n    }\n\n    async deleteKnowledgeCollectionNamedGraphs(implementationName, repository, namedGraphs) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.deleteKnowledgeCollectionNamedGraphs(repository, namedGraphs);\n        }\n    }\n\n    async getKnowledgeCollectionNamedGraphs(\n        implementationName,\n        repository,\n        ual,\n        knowledgeAssetId,\n        visibility,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeCollectionNamedGraphs(\n                repository,\n                ual,\n                knowledgeAssetId,\n                visibility,\n                timeout,\n            );\n        }\n    }\n\n    async getKnowledgeCollectionNamedGraphsInBatch(\n        implementationName,\n        repository,\n        uals,\n        visibility,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeCollectionNamedGraphsInBatch(repository, uals, visibility);\n        }\n    }\n\n    async getKnowledgeCollectionNamedGraphsOld(\n        implementationName,\n        repository,\n        ual,\n        tokenIds,\n        visibility,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeCollectionNamedGraphsOld(\n                repository,\n                ual,\n                tokenIds,\n                visibility,\n                timeout,\n            );\n        }\n    }\n\n    async checkIfKnowledgeAssetExists(implementationName, repository, kaUAL) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.checkIfKnowledgeAssetExists(\n                repository,\n                kaUAL,\n            );\n        }\n    }\n\n    async getKnowledgeCollectionNamedGraphsOldInBatch(\n        implementationName,\n        repository,\n        tokenIds,\n        visibility,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.getKnowledgeCollectionNamedGraphsOldInBatch(\n                repository,\n                tokenIds,\n                visibility,\n                timeout,\n            );\n        }\n    }\n\n    async getMetadataInBatch(implementationName, repository, uals) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getMetadataInBatch(\n                repository,\n                uals,\n            );\n        }\n    }\n\n    async knowledgeCollectionNamedGraphsExist(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.knowledgeCollectionNamedGraphsExist(repository, ual);\n        }\n    }\n\n    async deleteKnowledgeAssetNamedGraph(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.deleteKnowledgeAssetNamedGraph(\n                repository,\n                ual,\n            );\n        }\n    }\n\n    async getKnowledgeAssetNamedGraph(implementationName, repository, ual, visibility, timeout) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getKnowledgeAssetNamedGraph(\n                repository,\n                ual,\n                visibility,\n                timeout,\n            );\n        }\n    }\n\n    async knowledgeAssetNamedGraphExists(implementationName, repository, name) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.knowledgeAssetNamedGraphExists(\n                repository,\n                name,\n            );\n        }\n    }\n\n    async insertKnowledgeCollectionMetadata(\n        implementationName,\n        repository,\n        metadataNQuads,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.insertKnowledgeCollectionMetadata(repository, metadataNQuads, timeout);\n        }\n    }\n\n    async deleteKnowledgeCollectionMetadata(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.deleteKnowledgeCollectionMetadata(repository, ual);\n        }\n    }\n\n    async deletePublishTimestampMetadata(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.deletePublishTimestampMetadata(\n                repository,\n                ual,\n            );\n        }\n    }\n\n    async getKnowledgeCollectionMetadata(implementationName, repository, ual, timeout) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getKnowledgeCollectionMetadata(\n                repository,\n                ual,\n                timeout,\n            );\n        }\n    }\n\n    async getKnowledgeAssetMetadata(implementationName, repository, ual, timeout) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getKnowledgeAssetMetadata(\n                repository,\n                ual,\n                timeout,\n            );\n        }\n    }\n\n    async knowledgeCollectionMetadataExists(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(\n                implementationName,\n            ).module.knowledgeCollectionMetadataExists(repository, ual);\n        }\n    }\n\n    async getLatestAssertionId(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getLatestAssertionId(\n                repository,\n                ual,\n            );\n        }\n    }\n\n    async construct(implementationName, repository, query, timeout) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.construct(\n                repository,\n                query,\n                timeout,\n            );\n        }\n    }\n\n    async select(implementationName, repository, query, timeout) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.select(\n                repository,\n                query,\n                timeout,\n            );\n        }\n    }\n\n    async queryVoid(implementationName, repository, query) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.queryVoid(repository, query);\n        }\n    }\n\n    async deleteRepository(implementationName, repository) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.deleteRepository(repository);\n        }\n    }\n\n    async findAllNamedGraphsByUAL(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.findAllNamedGraphsByUAL(\n                repository,\n                ual,\n            );\n        }\n    }\n\n    async findAllSubjectsWithGraphNames(implementationName, repository, ual) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.findAllSubjectsWithGraphNames(\n                implementationName,\n                repository,\n                ual,\n            );\n        }\n    }\n\n    async ask(implementationName, repository, query) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.ask(repository, query);\n        }\n    }\n\n    async insertAssertionBatch(\n        implementationName,\n        repository,\n        insertMap,\n        metadata,\n        createdMetadata,\n        currentNamedGraphTriples,\n        timeout,\n    ) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.insertAssertionBatch(\n                repository,\n                insertMap,\n                metadata,\n                createdMetadata,\n                currentNamedGraphTriples,\n                timeout,\n            );\n        }\n    }\n\n    getName() {\n        return 'tripleStore';\n    }\n\n    // OLD REPOSITORIES SUPPORT\n\n    async getV6Assertion(implementationName, repository, assertionId) {\n        if (this.getImplementation(implementationName)) {\n            return this.getImplementation(implementationName).module.getV6Assertion(\n                repository,\n                assertionId,\n            );\n        }\n    }\n}\n\nexport default TripleStoreModuleManager;\n"
  },
  {
    "path": "src/modules/validation/implementation/merkle-validation.js",
    "content": "import { kcTools } from 'assertion-tools';\n\nclass MerkleValidation {\n    async initialize(config, logger) {\n        this.config = config;\n        this.logger = logger;\n    }\n\n    async calculateRoot(assertion) {\n        return kcTools.calculateMerkleRoot(assertion);\n    }\n}\n\nexport default MerkleValidation;\n"
  },
  {
    "path": "src/modules/validation/validation-module-manager.js",
    "content": "import BaseModuleManager from '../base-module-manager.js';\n\nclass ValidationModuleManager extends BaseModuleManager {\n    getName() {\n        return 'validation';\n    }\n\n    async calculateRoot(assertion) {\n        if (this.initialized) {\n            if (!assertion) {\n                throw new Error('Calculation failed: Assertion cannot be null or undefined.');\n            }\n            return this.getImplementation().module.calculateRoot(assertion);\n        }\n        throw new Error('Validation module is not initialized.');\n    }\n}\n\nexport default ValidationModuleManager;\n"
  },
  {
    "path": "src/service/ask-service.js",
    "content": "import OperationService from './operation-service.js';\nimport {\n    OPERATION_ID_STATUS,\n    NETWORK_PROTOCOLS,\n    ERROR_TYPE,\n    OPERATIONS,\n    OPERATION_REQUEST_STATUS,\n    ASK_BATCH_SIZE,\n} from '../constants/constants.js';\n\nclass AskService extends OperationService {\n    constructor(ctx) {\n        super(ctx);\n\n        this.operationName = OPERATIONS.ASK;\n        this.networkProtocols = NETWORK_PROTOCOLS.ASK;\n        this.errorType = ERROR_TYPE.ASK.ASK_ERROR;\n        this.completedStatuses = [\n            OPERATION_ID_STATUS.ASK.ASK_FETCH_FROM_NODES_END,\n            OPERATION_ID_STATUS.ASK.ASK_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ];\n    }\n\n    async processResponse(command, responseStatus, responseData) {\n        const { operationId, blockchain, numberOfFoundNodes, leftoverNodes, batchSize } =\n            command.data;\n\n        const responseStatusesFromDB = await this.getResponsesStatuses(\n            responseStatus,\n            responseData.errorMessage,\n            operationId,\n        );\n\n        const { completedNumber, failedNumber } = responseStatusesFromDB[operationId];\n\n        const totalResponses = completedNumber + failedNumber;\n        const isAllNodesResponded = numberOfFoundNodes === totalResponses;\n        const isBatchCompleted = totalResponses % batchSize === 0;\n\n        const minimumNumberOfNodeReplications =\n            command.data.minimumNumberOfNodeReplications ?? numberOfFoundNodes;\n\n        this.logger.debug(\n            `Processing ${\n                this.operationName\n            } response with status: ${responseStatus} for operationId: ${operationId}. Total number of nodes: ${numberOfFoundNodes}, number of nodes in batch: ${Math.min(\n                numberOfFoundNodes,\n                batchSize,\n            )} number of leftover nodes: ${\n                leftoverNodes.length\n            }, number of responses: ${totalResponses}, Completed: ${completedNumber}, Failed: ${failedNumber}`,\n        );\n        if (responseData.errorMessage) {\n            this.logger.trace(\n                `Error message for operation id: ${operationId} : ${responseData.errorMessage}`,\n            );\n        }\n\n        if (\n            responseStatus === OPERATION_REQUEST_STATUS.COMPLETED &&\n            completedNumber === minimumNumberOfNodeReplications\n        ) {\n            await this.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                {\n                    completedNodes: completedNumber,\n                    allNodesReplicatedData: true,\n                },\n                [...this.completedStatuses],\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        } else if (\n            completedNumber < minimumNumberOfNodeReplications &&\n            (isAllNodesResponded || isBatchCompleted)\n        ) {\n            const potentialCompletedNumber = completedNumber + leftoverNodes.length;\n\n            await this.operationIdService.cacheOperationIdDataToFile(operationId, {\n                completedNodes: completedNumber,\n                allNodesReplicatedData: false,\n            });\n\n            // Still possible to meet minimumNumberOfNodeReplications, schedule leftover nodes\n            if (\n                leftoverNodes.length > 0 &&\n                potentialCompletedNumber >= minimumNumberOfNodeReplications\n            ) {\n                await this.scheduleOperationForLeftoverNodes(command.data, leftoverNodes);\n            } else {\n                // Not enough potential responses to meet minimumNumberOfNodeReplications, or no leftover nodes\n                await this.markOperationAsFailed(\n                    operationId,\n                    blockchain,\n                    `Unable to replicate data on the network!`,\n                    this.errorType,\n                );\n                this.logResponsesSummary(completedNumber, failedNumber);\n            }\n        }\n    }\n\n    getBatchSize(batchSize = null) {\n        return batchSize ?? ASK_BATCH_SIZE;\n    }\n}\n\nexport default AskService;\n"
  },
  {
    "path": "src/service/auth-service.js",
    "content": "import ipLib from 'ip';\nimport jwtUtil from './util/jwt-util.js';\n\nclass AuthService {\n    constructor(ctx) {\n        this._authConfig = ctx.config.auth;\n        this._repository = ctx.repositoryModuleManager;\n        this._logger = ctx.logger;\n    }\n\n    /**\n     * Authenticate users based on provided ip and token\n     * @param ip\n     * @param token\n     * @returns {boolean}\n     */\n    async authenticate(ip, token) {\n        const isWhitelisted = this._isIpWhitelisted(ip);\n        const isTokenValid = await this._isTokenValid(token);\n\n        const tokenAuthEnabled = this._authConfig.tokenBasedAuthEnabled;\n        const ipAuthEnabled = this._authConfig.ipBasedAuthEnabled;\n        const requiresBoth = this._authConfig.bothIpAndTokenAuthRequired;\n\n        let isAuthenticated = false;\n        if (tokenAuthEnabled && ipAuthEnabled) {\n            isAuthenticated = requiresBoth\n                ? isWhitelisted && isTokenValid\n                : isWhitelisted || isTokenValid;\n        } else {\n            isAuthenticated = isWhitelisted && isTokenValid;\n        }\n\n        if (!isAuthenticated) {\n            this._logMessage('Received unauthenticated request.');\n        }\n\n        return isAuthenticated;\n    }\n\n    /**\n     * Checks whether user whose token is provided has abilities for system operation\n     * @param token\n     * @param systemOperation\n     * @returns {Promise<boolean|*>}\n     */\n    async isAuthorized(token, systemOperation) {\n        if (!this._authConfig.tokenBasedAuthEnabled) {\n            return true;\n        }\n\n        /* \n            If IP is whitelisted and both IP and Token Auth is NOT required pass authorization check.\n            Authentication middleware checks if IP is white listed before authorization middleware.\n        */\n        if (!(await this._isTokenValid(token))) {\n            if (\n                !this._authConfig.bothIpAndTokenAuthRequired &&\n                this._authConfig.ipBasedAuthEnabled\n            ) {\n                return true;\n            }\n            return false;\n        }\n\n        const tokenId = jwtUtil.getPayload(token).jti;\n        const abilities = await this._repository.getTokenAbilities(tokenId);\n\n        const isAuthorized = abilities.includes(systemOperation);\n\n        const logMessage = isAuthorized\n            ? `Token ${tokenId} is successfully authenticated and authorized.`\n            : `Received unauthorized request.`;\n\n        this._logMessage(logMessage);\n\n        return isAuthorized;\n    }\n\n    /**\n     * Determines whether operation is listed in config.auth.publicOperations\n     * @param operationName\n     * @returns {boolean}\n     */\n    isPublicOperation(operationName) {\n        if (!Array.isArray(this._authConfig.publicOperations)) {\n            return false;\n        }\n\n        return this._authConfig.publicOperations.some(\n            (publicOperation) =>\n                publicOperation === `V0/${operationName}` || publicOperation === operationName,\n        );\n    }\n\n    /**\n     * Validates token structure and revoked status\n     * If ot-node is configured not to do a token based auth, it will return true\n     * @param token\n     * @returns {boolean}\n     * @private\n     */\n    async _isTokenValid(token) {\n        if (!this._authConfig.tokenBasedAuthEnabled) {\n            return true;\n        }\n\n        if (!token) {\n            return false;\n        }\n\n        if (!jwtUtil.validateJWT(token)) {\n            return false;\n        }\n\n        const isRevoked = await this._isTokenRevoked(token);\n\n        return isRevoked !== null && !isRevoked;\n    }\n\n    /**\n     * Checks whether provided ip is whitelisted in config\n     * Returns false if ip based auth is disabled\n     * @param reqIp\n     * @returns {boolean}\n     * @private\n     */\n    _isIpWhitelisted(reqIp) {\n        if (!this._authConfig.ipBasedAuthEnabled) {\n            return true;\n        }\n\n        for (const whitelistedIp of this._authConfig.ipWhitelist) {\n            let isEqual = false;\n\n            try {\n                isEqual = ipLib.isEqual(reqIp, whitelistedIp);\n            } catch (e) {\n                // if ip is not valid IP isEqual should remain false\n            }\n\n            if (isEqual) {\n                return true;\n            }\n        }\n\n        return false;\n    }\n\n    /**\n     * Checks whether provided token is revoked\n     * Returns false if token based auth is disabled\n     * @param token\n     * @returns {Promise<boolean|*>|boolean}\n     * @private\n     */\n    _isTokenRevoked(token) {\n        if (!this._authConfig.tokenBasedAuthEnabled) {\n            return false;\n        }\n\n        const tokenId = jwtUtil.getPayload(token).jti;\n\n        return this._repository.isTokenRevoked(tokenId);\n    }\n\n    /**\n     * Logs message if loggingEnabled is set to true\n     * @param message\n     * @private\n     */\n    _logMessage(message) {\n        if (this._authConfig.loggingEnabled) {\n            this._logger.info(`[AUTH] ${message}`);\n        }\n    }\n}\n\nexport default AuthService;\n"
  },
  {
    "path": "src/service/batch-get-service.js",
    "content": "import OperationService from './operation-service.js';\nimport {\n    OPERATION_ID_STATUS,\n    NETWORK_PROTOCOLS,\n    ERROR_TYPE,\n    OPERATIONS,\n} from '../constants/constants.js';\n\nclass BatchGetService extends OperationService {\n    constructor(ctx) {\n        super(ctx);\n\n        this.operationName = OPERATIONS.BATCH_GET;\n        this.networkProtocols = NETWORK_PROTOCOLS.BATCH_GET;\n        this.errorType = ERROR_TYPE.BATCH_GET.BATCH_GET_ERROR;\n        this.completedStatuses = [\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_FETCH_FROM_NODES_END,\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ];\n    }\n}\n\nexport default BatchGetService;\n"
  },
  {
    "path": "src/service/blockchain-events-service.js",
    "content": "class BlockchainEventsService {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n\n        this.blockchainEventsModuleManager = ctx.blockchainEventsModuleManager;\n    }\n\n    initializeBlockchainEventsServices() {\n        this.blockchainEventsServicesImplementations = {};\n        for (const implementationName of this.blockchainEventsModuleManager.getImplementationNames()) {\n            for (const blockchain in this.blockchainEventsModuleManager.getImplementation(\n                implementationName,\n            ).module.blockchains) {\n                this.blockchainEventsServicesImplementations[blockchain] = implementationName;\n            }\n        }\n    }\n\n    getContractAddress(blockchain, contractName) {\n        return this.blockchainEventsModuleManager.getContractAddress(\n            this.blockchainEventsServicesImplementations[blockchain],\n            blockchain,\n            contractName,\n        );\n    }\n\n    updateContractAddress(blockchain, contractName, contractAddress) {\n        return this.blockchainEventsModuleManager.updateContractAddress(\n            this.blockchainEventsServicesImplementations[blockchain],\n            blockchain,\n            contractName,\n            contractAddress,\n        );\n    }\n\n    async getBlock(blockchain, tag = 'latest') {\n        return this.blockchainEventsModuleManager.getBlock(\n            this.blockchainEventsServicesImplementations[blockchain],\n            blockchain,\n            tag,\n        );\n    }\n\n    async getPastEvents(\n        blockchain,\n        contractNames,\n        eventsToFilter,\n        lastCheckedBlock,\n        lastCheckedTimestamp,\n        currentBlock,\n    ) {\n        return this.blockchainEventsModuleManager.getPastEvents(\n            this.blockchainEventsServicesImplementations[blockchain],\n            blockchain,\n            contractNames,\n            eventsToFilter,\n            lastCheckedBlock,\n            lastCheckedTimestamp,\n            currentBlock,\n        );\n    }\n}\n\nexport default BlockchainEventsService;\n"
  },
  {
    "path": "src/service/claim-rewards-service.js",
    "content": "import { CLAIM_REWARDS_BATCH_SIZE, CLAIM_REWARDS_INTERVAL } from '../constants/constants.js';\n\nclass ClaimRewardsService {\n    constructor(ctx) {\n        this.ctx = ctx;\n        this.logger = ctx.logger;\n        this.ualService = ctx.ualService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.validationService = ctx.validationService;\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n    }\n\n    async initialize() {\n        this.logger.info('[CLAIM] Initializing ClaimRewardsService');\n        const promises = [];\n        for (const blockchainId of this.blockchainModuleManager.getImplementationNames()) {\n            this.logger.info(\n                `[CLAIM] Initializing claim rewards service for blockchain ${blockchainId}`,\n            );\n            promises.push(this.claimRewardsMechanism(blockchainId));\n        }\n        await Promise.all(promises);\n        this.logger.info('[CLAIM] ClaimRewardsService initialization completed');\n    }\n\n    async claimRewardsMechanism(blockchainId) {\n        this.logger.debug(\n            `[CLAIM] Setting up claim rewards mechanism for blockchain ${blockchainId}`,\n        );\n        // Flag to track if mechanism is running\n        let isRunning = false;\n\n        // Set up interval\n        const interval = setInterval(async () => {\n            // Skip if already running\n            if (isRunning) {\n                this.logger.debug(\n                    `[CLAIM] Claim rewards mechanism for ${blockchainId} still running, skipping this interval`,\n                );\n                return;\n            }\n\n            try {\n                isRunning = true;\n                this.logger.debug(\n                    `[CLAIM] Starting claim rewards cycle for blockchain ${blockchainId}`,\n                );\n\n                // Proofing logic\n                await this.claimRewards(blockchainId);\n                this.logger.debug(\n                    `[CLAIM] Completed claim rewards cycle for blockchain ${blockchainId}`,\n                );\n            } catch (error) {\n                this.logger.error(\n                    `[CLAIM] Error in claim rewards mechanism for ${blockchainId}: ${error.message}, stack: ${error.stack}`,\n                );\n            } finally {\n                isRunning = false;\n            }\n        }, CLAIM_REWARDS_INTERVAL);\n\n        // Store interval reference for cleanup\n        this[`${blockchainId}Interval`] = interval;\n        this.logger.info(\n            `[CLAIM] Claim rewards mechanism initialized for blockchain ${blockchainId}`,\n        );\n\n        // Run immediately on startup\n        try {\n            isRunning = true;\n            this.logger.debug(\n                `[CLAIM] Running initial claim rewards cycle for blockchain ${blockchainId}`,\n            );\n            await this.claimRewards(blockchainId);\n        } catch (error) {\n            this.logger.error(\n                `[CLAIM] Error in initial claim rewards run for ${blockchainId}: ${error.message}, stack: ${error.stack}`,\n            );\n            // this.operationIdService.emitChangeEvent(\n            //     'CLAIM_REWARDS_ERROR',\n            //     this.generateOperationId(blockchainId, 0, 0),\n            //     blockchainId,\n            //     error.message,\n            //     error.stack,\n            // );\n        } finally {\n            isRunning = false;\n        }\n    }\n\n    async claimRewards(blockchainId) {\n        const identityId = await this.blockchainModuleManager.getIdentityId(blockchainId);\n        const nodeDelegatorAddresses = await this.blockchainModuleManager.getDelegators(\n            blockchainId,\n            identityId,\n        );\n        const lastClaimedEpochAddressesMap = {};\n        await Promise.all(\n            nodeDelegatorAddresses.map(async (delegatorAddress) => {\n                const lastClaimedEpoch = await this.blockchainModuleManager.getLastClaimedEpoch(\n                    blockchainId,\n                    identityId,\n                    delegatorAddress,\n                );\n                if (!lastClaimedEpochAddressesMap[`${lastClaimedEpoch}`]) {\n                    lastClaimedEpochAddressesMap[`${lastClaimedEpoch}`] = [];\n                }\n                lastClaimedEpochAddressesMap[`${lastClaimedEpoch}`].push(delegatorAddress);\n            }),\n        );\n        const currentEpoch = Number(\n            (await this.blockchainModuleManager.getCurrentEpoch(blockchainId)).toString(),\n        );\n        if (lastClaimedEpochAddressesMap['0'] && lastClaimedEpochAddressesMap['0'].length > 0) {\n            // This means delegator never claimed for the node, but is in the list of delegators\n            // This means node never claimed and delegated before introduction of random sampling\n            // If he staked or claimed before the value would have been set correctly\n            const delegatorAddresses = lastClaimedEpochAddressesMap['0'];\n            await Promise.all(\n                delegatorAddresses.map(async (delegatorAddress) => {\n                    const hasEverDelegated = await this.blockchainModuleManager.hasEverDelegated(\n                        blockchainId,\n                        identityId,\n                        delegatorAddress,\n                    );\n                    // TODO: How will this impact mainnet where this function landed at same time as proofing\n                    if (!hasEverDelegated) {\n                        if (lastClaimedEpochAddressesMap[`${currentEpoch - 1}`]) {\n                            lastClaimedEpochAddressesMap[`${currentEpoch - 1}`].push(\n                                ...delegatorAddresses,\n                            );\n                        } else {\n                            lastClaimedEpochAddressesMap[`${currentEpoch - 1}`] =\n                                delegatorAddresses;\n                        }\n                    }\n                }),\n            );\n        }\n        if (lastClaimedEpochAddressesMap[`0`]) {\n            delete lastClaimedEpochAddressesMap[`0`];\n        }\n        const sortedEpochs = Object.keys(lastClaimedEpochAddressesMap)\n            .map(Number) // convert keys to numbers\n            .sort((a, b) => a - b); // sort numerically ascending\n\n        for (let i = 0; i < sortedEpochs.length; i += 1) {\n            const epoch = sortedEpochs[i];\n            const delegatorAddresses = lastClaimedEpochAddressesMap[epoch.toString()];\n            if (epoch + 1 !== currentEpoch) {\n                for (let j = 0; j < delegatorAddresses.length; j += CLAIM_REWARDS_BATCH_SIZE) {\n                    const batch = delegatorAddresses.slice(j, j + CLAIM_REWARDS_BATCH_SIZE);\n                    try {\n                        const batchClaimed =\n                            // eslint-disable-next-line no-await-in-loop\n                            await this.blockchainModuleManager.batchClaimDelegatorRewards(\n                                blockchainId,\n                                identityId,\n                                [epoch + 1],\n                                batch,\n                            );\n                        if (batchClaimed.success) {\n                            this.logger.info(\n                                `[CLAIM] Claimed rewards for batch ${batch} in epoch ${\n                                    epoch + 1\n                                } on ${blockchainId}`,\n                            );\n                            // If there are more epochs for this batch move them to next batch\n                            if (lastClaimedEpochAddressesMap[`${epoch + 1}`]) {\n                                lastClaimedEpochAddressesMap[`${epoch + 1}`].push(...batch);\n                            } else {\n                                lastClaimedEpochAddressesMap[`${epoch + 1}`] = batch;\n                                // lastClaimedEpochAddressesMap[`${epoch + 1}`] didn't exist before so we need to also update sortedEpochs\n                                // splice handles if i + 1 === sortedEpochs.length\n                                sortedEpochs.splice(i + 1, 0, epoch + 1);\n                            }\n                        } else {\n                            this.logger.error(\n                                `[CLAIM] Error claiming rewards for batch ${batch} in epoch ${\n                                    epoch + 1\n                                } on ${blockchainId}`,\n                                batchClaimed.error,\n                            );\n                        }\n                    } catch (error) {\n                        this.logger.error(\n                            `[CLAIM] Error claiming rewards for batch ${batch} in epoch ${\n                                epoch + 1\n                            } on ${blockchainId}`,\n                            error,\n                        );\n                    }\n                }\n            }\n        }\n    }\n}\n\nexport default ClaimRewardsService;\n"
  },
  {
    "path": "src/service/crypto-service.js",
    "content": "import ethers from 'ethers';\n\nclass CryptoService {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n    }\n\n    toBigNumber(value) {\n        return ethers.BigNumber.from(value);\n    }\n\n    keccak256(data) {\n        if (!ethers.utils.isBytesLike(data)) {\n            const bytesLikeData = ethers.utils.toUtf8Bytes(data);\n            return ethers.utils.keccak256(bytesLikeData);\n        }\n        return ethers.utils.keccak256(data);\n    }\n\n    sha256(data) {\n        if (!ethers.utils.isBytesLike(data)) {\n            const bytesLikeData = ethers.utils.toUtf8Bytes(data);\n            return ethers.utils.sha256(bytesLikeData);\n        }\n        return ethers.utils.sha256(data);\n    }\n\n    encodePacked(types, values) {\n        return ethers.utils.solidityPack(types, values);\n    }\n\n    keccak256EncodePacked(types, values) {\n        return ethers.utils.solidityKeccak256(types, values);\n    }\n\n    sha256EncodePacked(types, values) {\n        return ethers.utils.soliditySha256(types, values);\n    }\n\n    convertUint8ArrayToHex(uint8Array) {\n        return ethers.utils.hexlify(uint8Array);\n    }\n\n    convertAsciiToHex(string) {\n        return this.convertUint8ArrayToHex(ethers.utils.toUtf8Bytes(string));\n    }\n\n    convertHexToAscii(hexString) {\n        return ethers.utils.toUtf8String(hexString);\n    }\n\n    convertBytesToUint8Array(bytesLikeData) {\n        return ethers.utils.arrayify(bytesLikeData);\n    }\n\n    convertToWei(value, fromUnit = 'ether') {\n        return ethers.utils.parseUnits(value.toString(), fromUnit);\n    }\n\n    convertFromWei(value, toUnit = 'ether') {\n        return ethers.utils.formatUnits(value, toUnit);\n    }\n\n    splitSignature(flatSignature) {\n        return ethers.utils.splitSignature(flatSignature);\n    }\n}\n\nexport default CryptoService;\n"
  },
  {
    "path": "src/service/dependency-injection.js",
    "content": "import awilix from 'awilix';\n\nclass DependencyInjection {\n    static async initialize() {\n        const container = awilix.createContainer({\n            injectionMode: awilix.InjectionMode.PROXY,\n        });\n\n        await container.loadModules(\n            [\n                'src/controllers/**/*.js',\n                'src/service/*.js',\n                'src/commands/**/**/**/*.js',\n                'src/commands/*.js',\n                'src/modules/base-module-manager.js',\n                'src/modules/**/*module-manager.js',\n            ],\n            {\n                esModules: true,\n                formatName: 'camelCase',\n                resolverOptions: {\n                    lifetime: awilix.Lifetime.SINGLETON,\n                    register: awilix.asClass,\n                },\n            },\n        );\n\n        return container;\n    }\n\n    static registerValue(container, valueName, value) {\n        container.register({\n            [valueName]: awilix.asValue(value),\n        });\n    }\n}\n\nexport default DependencyInjection;\n"
  },
  {
    "path": "src/service/file-service.js",
    "content": "import os from 'os';\nimport path from 'path';\nimport {\n    mkdir,\n    writeFile,\n    readFile,\n    unlink,\n    stat,\n    readdir,\n    rm,\n    appendFile,\n    chmod,\n} from 'fs/promises';\nimport appRootPath from 'app-root-path';\nimport {\n    BLS_KEY_DIRECTORY,\n    BLS_KEY_FILENAME,\n    MIGRATION_FOLDER,\n    NODE_ENVIRONMENTS,\n} from '../constants/constants.js';\n\nclass FileService {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n    }\n\n    getFileExtension(filePath) {\n        return path.extname(filePath).toLowerCase();\n    }\n\n    /**\n     * Write contents to file\n     * @param directory\n     * @param filename\n     * @param data\n     * @returns {Promise}\n     */\n    async writeContentsToFile(directory, filename, data, log = true, flag = 'w') {\n        if (log) {\n            this.logger.debug(`Saving file with name: ${filename} in the directory: ${directory}`);\n        }\n        await mkdir(directory, { recursive: true });\n        const fullpath = path.join(directory, filename);\n        await writeFile(fullpath, data, { flag });\n        return fullpath;\n    }\n\n    async appendContentsToFile(directory, filename, data, log = true) {\n        if (log) {\n            this.logger.debug(`Saving file with name: ${filename} in the directory: ${directory}`);\n        }\n        await mkdir(directory, { recursive: true });\n        const fullPath = path.join(directory, filename);\n\n        await appendFile(fullPath, data);\n\n        return fullPath;\n    }\n\n    async readDirectory(dirPath) {\n        this.logger.debug(`Reading folder at path: ${dirPath}`);\n        try {\n            return readdir(dirPath);\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                throw Error(`Folder not found at path: ${dirPath}`);\n            }\n            throw error;\n        }\n    }\n\n    async stat(filePath) {\n        return stat(filePath);\n    }\n\n    async pathExists(fileOrDirPath) {\n        try {\n            await stat(fileOrDirPath);\n            return true;\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                return false;\n            }\n            throw error;\n        }\n    }\n\n    async readFile(filePath, convertToJSON = false) {\n        this.logger.debug(`Reading file: ${filePath}, converting to json: ${convertToJSON}`);\n        try {\n            const data = await readFile(filePath);\n            return convertToJSON ? JSON.parse(data) : data.toString();\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                throw Error(`File not found at path: ${filePath}`);\n            }\n            throw error;\n        }\n    }\n\n    async removeFile(filePath) {\n        this.logger.trace(`Removing file at path: ${filePath}`);\n\n        try {\n            await unlink(filePath);\n            return true;\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                this.logger.debug(`File not found at path: ${filePath}`);\n                return false;\n            }\n            throw error;\n        }\n    }\n\n    async removeFolder(folderPath) {\n        this.logger.debug(`Removing folder at path: ${folderPath}`);\n        try {\n            await rm(folderPath, { recursive: true });\n            return true;\n        } catch (error) {\n            if (error.code === 'ENOENT') {\n                this.logger.debug(`Folder not found at path: ${folderPath}`);\n                return false;\n            }\n            throw error;\n        }\n    }\n\n    getBinariesFolderPath() {\n        return path.join(appRootPath.path, 'bin');\n    }\n\n    getBinaryPath(binary) {\n        let binaryName = binary;\n        if (process.platform === 'win32') {\n            binaryName += '.exe';\n        }\n        return path.join(this.getBinariesFolderPath(), process.platform, process.arch, binaryName);\n    }\n\n    async makeBinaryExecutable(binary) {\n        const binaryPath = this.getBinaryPath(binary);\n        if (os.platform() !== 'win32') {\n            await chmod(binaryPath, '755', (err) => {\n                if (err) {\n                    throw err;\n                }\n                this.logger.debug(`Permissions for binary ${binaryPath} have been set to 755.`);\n            });\n        }\n    }\n\n    getBLSSecretKeyFolderPath() {\n        return path.join(this.getDataFolderPath(), BLS_KEY_DIRECTORY);\n    }\n\n    getBLSSecretKeyPath() {\n        return path.join(this.getBLSSecretKeyFolderPath(), BLS_KEY_FILENAME);\n    }\n\n    getDataFolderPath() {\n        if (\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.DEVNET ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.TESTNET ||\n            process.env.NODE_ENV === NODE_ENVIRONMENTS.MAINNET\n        ) {\n            return path.join(appRootPath.path, '..', this.config.appDataPath);\n        }\n        return path.join(appRootPath.path, this.config.appDataPath);\n    }\n\n    getUpdateFilePath() {\n        return path.join(this.getDataFolderPath(), 'UPDATED');\n    }\n\n    getMigrationFolderPath() {\n        return path.join(this.getDataFolderPath(), MIGRATION_FOLDER);\n    }\n\n    getOperationIdCachePath() {\n        return path.join(this.getDataFolderPath(), 'operation_id_cache');\n    }\n\n    getOperationIdDocumentPath(operationId) {\n        return path.join(this.getOperationIdCachePath(), operationId);\n    }\n\n    getPendingStorageCachePath() {\n        return path.join(this.getDataFolderPath(), 'pending_storage_cache');\n    }\n\n    getPendingStorageDocumentPath(operationId) {\n        return path.join(this.getPendingStorageCachePath(), operationId);\n    }\n\n    getSignatureStorageCachePath() {\n        return path.join(this.getDataFolderPath(), 'signature_storage_cache');\n    }\n\n    getSignatureStorageFolderPath(folderName) {\n        return path.join(this.getSignatureStorageCachePath(), folderName);\n    }\n\n    getSignatureStorageDocumentPath(folderName, operationId) {\n        return path.join(this.getSignatureStorageFolderPath(folderName), operationId);\n    }\n\n    getParentDirectory(filePath) {\n        return path.dirname(filePath);\n    }\n}\n\nexport default FileService;\n"
  },
  {
    "path": "src/service/finality-service.js",
    "content": "import OperationService from './operation-service.js';\nimport {\n    OPERATION_ID_STATUS,\n    NETWORK_PROTOCOLS,\n    ERROR_TYPE,\n    OPERATIONS,\n    OPERATION_REQUEST_STATUS,\n    FINALITY_BATCH_SIZE,\n    FINALITY_MIN_NUM_OF_NODE_REPLICATIONS,\n} from '../constants/constants.js';\n\nclass FinalityService extends OperationService {\n    constructor(ctx) {\n        super(ctx);\n\n        this.operationName = OPERATIONS.FINALITY;\n        this.networkProtocols = NETWORK_PROTOCOLS.FINALITY;\n        this.errorType = ERROR_TYPE.FINALITY.FINALITY_ERROR;\n        this.completedStatuses = [\n            OPERATION_ID_STATUS.PUBLISH_FINALIZATION.PUBLISH_FINALIZATION_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ];\n        this.ualService = ctx.ualService;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.paranetService = ctx.paranetService;\n    }\n\n    async processResponse(operationId, blockchain, responseStatus, responseData) {\n        const responseStatusesFromDB = await this.getResponsesStatuses(\n            responseStatus,\n            responseData.errorMessage,\n            operationId,\n        );\n\n        const { completedNumber, failedNumber } = responseStatusesFromDB[operationId];\n\n        this.logger.debug(\n            `Processing ${this.operationName} response with status: ${responseStatus} for operationId: ${operationId}. ` +\n                `Completed: ${completedNumber}, Failed: ${failedNumber}`,\n        );\n        if (responseData.errorMessage) {\n            this.logger.trace(\n                `Error message for operation id: ${operationId} : ${responseData.errorMessage}`,\n            );\n        }\n\n        if (responseStatus === OPERATION_REQUEST_STATUS.COMPLETED) {\n            await this.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                {\n                    completedNodes: 1,\n                    allNodesReplicatedData: true,\n                },\n                [...this.completedStatuses],\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        } else {\n            await this.markOperationAsFailed(\n                operationId,\n                blockchain,\n                `Unable to send ACK for finalization!`,\n                this.errorType,\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        }\n    }\n\n    getBatchSize(batchSize = null) {\n        return batchSize ?? FINALITY_BATCH_SIZE;\n    }\n\n    getMinAckResponses(minimumNumberOfNodeReplications = null) {\n        return minimumNumberOfNodeReplications ?? FINALITY_MIN_NUM_OF_NODE_REPLICATIONS;\n    }\n}\n\nexport default FinalityService;\n"
  },
  {
    "path": "src/service/get-service.js",
    "content": "import OperationService from './operation-service.js';\nimport {\n    OPERATION_ID_STATUS,\n    NETWORK_PROTOCOLS,\n    ERROR_TYPE,\n    OPERATIONS,\n    OPERATION_REQUEST_STATUS,\n    GET_BATCH_SIZE,\n    GET_MIN_NUM_OF_NODE_REPLICATIONS,\n} from '../constants/constants.js';\n\nclass GetService extends OperationService {\n    constructor(ctx) {\n        super(ctx);\n\n        this.operationName = OPERATIONS.GET;\n        this.networkProtocols = NETWORK_PROTOCOLS.GET;\n        this.errorType = ERROR_TYPE.GET.GET_ERROR;\n        this.completedStatuses = [\n            OPERATION_ID_STATUS.GET.GET_FETCH_FROM_NODES_END,\n            OPERATION_ID_STATUS.GET.GET_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ];\n    }\n\n    async processResponse(command, responseStatus, responseData) {\n        const {\n            operationId,\n            blockchain,\n            numberOfFoundNodes,\n            leftoverNodes,\n            batchSize,\n            minAckResponses,\n            assertionId,\n        } = command.data;\n\n        const responseStatusesFromDB = await this.getResponsesStatuses(\n            responseStatus,\n            responseData.errorMessage,\n            operationId,\n        );\n\n        const { completedNumber, failedNumber } = responseStatusesFromDB[operationId];\n\n        const totalResponses = completedNumber + failedNumber;\n        const isAllNodesResponded = numberOfFoundNodes === totalResponses;\n        const isBatchCompleted = totalResponses % batchSize === 0;\n\n        this.logger.debug(\n            `Processing ${\n                this.operationName\n            } response with status: ${responseStatus} for operationId: ${operationId}. Total number of nodes: ${numberOfFoundNodes}, number of nodes in batch: ${Math.min(\n                numberOfFoundNodes,\n                batchSize,\n            )} number of leftover nodes: ${\n                leftoverNodes.length\n            }, number of responses: ${totalResponses}, Completed: ${completedNumber}, Failed: ${failedNumber}`,\n        );\n        if (responseData.errorMessage) {\n            this.logger.trace(\n                `Error message for operation id: ${operationId} : ${responseData.errorMessage}`,\n            );\n        }\n\n        if (\n            responseStatus === OPERATION_REQUEST_STATUS.COMPLETED &&\n            completedNumber === minAckResponses\n        ) {\n            await this.markOperationAsCompleted(operationId, blockchain, responseData, [\n                ...this.completedStatuses,\n            ]);\n            this.logResponsesSummary(completedNumber, failedNumber);\n        } else if (completedNumber < minAckResponses && (isAllNodesResponded || isBatchCompleted)) {\n            const potentialCompletedNumber = completedNumber + leftoverNodes.length;\n\n            // Still possible to meet minAckResponses, schedule leftover nodes\n            if (leftoverNodes.length > 0 && potentialCompletedNumber >= minAckResponses) {\n                await this.scheduleOperationForLeftoverNodes(command.data, leftoverNodes);\n            } else {\n                // Not enough potential responses to meet minAckResponses, or no leftover nodes\n                this.markOperationAsFailed(\n                    operationId,\n                    blockchain,\n                    `Unable to find assertion ${assertionId} on the network!`,\n                    this.errorType,\n                );\n                this.operationIdService.emitChangeEvent(\n                    OPERATION_ID_STATUS.GET.GET_FAILED,\n                    operationId,\n                );\n                this.logResponsesSummary(completedNumber, failedNumber);\n            }\n        }\n    }\n\n    getBatchSize(batchSize = null) {\n        return batchSize ?? GET_BATCH_SIZE;\n    }\n\n    getMinAckResponses(minimumNumberOfNodeReplications = null) {\n        return minimumNumberOfNodeReplications ?? GET_MIN_NUM_OF_NODE_REPLICATIONS;\n    }\n}\n\nexport default GetService;\n"
  },
  {
    "path": "src/service/json-schema-service.js",
    "content": "import path from 'path';\nimport appRootPath from 'app-root-path';\n\nclass JsonSchemaService {\n    constructor(ctx) {\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async loadSchema(version, schemaName, argumentsObject = {}) {\n        const schemaPath = path.resolve(\n            appRootPath.path,\n            `src/controllers/http-api/${version}/request-schema/${schemaName}-schema-${version}.js`,\n        );\n        const schemaModule = await import(schemaPath);\n        const schemaFunction = schemaModule.default;\n\n        if (schemaFunction.length !== 0) {\n            return schemaFunction(argumentsObject);\n        }\n\n        return schemaFunction();\n    }\n\n    async bidSuggestionSchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v0':\n            case 'v1':\n                schemaArgs.blockchainImplementationNames =\n                    this.blockchainModuleManager.getImplementationNames();\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'bid-suggestion', schemaArgs);\n    }\n\n    async publishSchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v0':\n            case 'v1':\n                schemaArgs.blockchainImplementationNames =\n                    this.blockchainModuleManager.getImplementationNames();\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'publish', schemaArgs);\n    }\n\n    async updateSchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v0':\n            case 'v1':\n                schemaArgs.blockchainImplementationNames =\n                    this.blockchainModuleManager.getImplementationNames();\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'update', schemaArgs);\n    }\n\n    async getSchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v0':\n            case 'v1':\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'get', schemaArgs);\n    }\n\n    async querySchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v0':\n            case 'v1':\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'query', schemaArgs);\n    }\n\n    async localStoreSchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v0':\n            case 'v1':\n                schemaArgs.blockchainImplementationNames =\n                    this.blockchainModuleManager.getImplementationNames();\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'local-store', schemaArgs);\n    }\n\n    async finalitySchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v1':\n                schemaArgs.blockchainImplementationNames =\n                    this.blockchainModuleManager.getImplementationNames();\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'finality', schemaArgs);\n    }\n\n    async askSchema(version) {\n        const schemaArgs = {};\n\n        switch (version) {\n            case 'v1':\n                schemaArgs.blockchainImplementationNames =\n                    this.blockchainModuleManager.getImplementationNames();\n                break;\n            default:\n                throw Error(`HTTP API version: ${version} isn't supported.`);\n        }\n\n        return this.loadSchema(version, 'ask', schemaArgs);\n    }\n}\n\nexport default JsonSchemaService;\n"
  },
  {
    "path": "src/service/messaging-service.js",
    "content": "import { NETWORK_MESSAGE_TYPES, OPERATION_REQUEST_STATUS } from '../constants/constants.js';\n\nclass MessagingService {\n    constructor(ctx) {\n        this.networkModuleManager = ctx.networkModuleManager;\n    }\n\n    async sendProtocolMessage(node, operationId, message, messageType, timeout) {\n        const response = await this.networkModuleManager.sendMessage(\n            node.protocol,\n            node.id,\n            messageType,\n            operationId,\n            message,\n            timeout,\n        );\n\n        this.networkModuleManager.removeCachedSession(operationId, node.id);\n        return response;\n    }\n\n    async handleProtocolResponse(response, operationService, blockchain, operationId) {\n        switch (response.header.messageType) {\n            case NETWORK_MESSAGE_TYPES.RESPONSES.BUSY:\n                return this.handleBusyResponse();\n            case NETWORK_MESSAGE_TYPES.RESPONSES.NACK:\n                return this.handleNackResponse(\n                    operationService,\n                    blockchain,\n                    operationId,\n                    response.data,\n                );\n            case NETWORK_MESSAGE_TYPES.RESPONSES.ACK:\n                return this.handleAckResponse(\n                    operationService,\n                    blockchain,\n                    operationId,\n                    response.data,\n                );\n            default:\n                return this.handleUnknownResponse(operationService, blockchain, operationId);\n        }\n    }\n\n    async handleBusyResponse() {\n        return { retry: true };\n    }\n\n    async handleAckResponse(operationService, blockchain, operationId, responseData) {\n        await operationService.processResponse(\n            operationId,\n            blockchain,\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            responseData,\n        );\n        return { success: true };\n    }\n\n    async handleNackResponse(operationService, blockchain, operationId, responseData) {\n        await operationService.processResponse(\n            operationId,\n            blockchain,\n            OPERATION_REQUEST_STATUS.FAILED,\n            {\n                errorMessage: `Received NACK response. Error: ${responseData.errorMessage}`,\n            },\n        );\n        return { failed: true };\n    }\n\n    async handleUnknownResponse(operationService, blockchain, operationId) {\n        await operationService.processResponse(\n            operationId,\n            blockchain,\n            OPERATION_REQUEST_STATUS.FAILED,\n            {\n                errorMessage: `Received unknown message type during`, // TODO: Add command name\n            },\n        );\n        return { failed: true };\n    }\n}\n\nexport default MessagingService;\n"
  },
  {
    "path": "src/service/operation-id-service.js",
    "content": "import { validate, v4 as uuidv4 } from 'uuid';\nimport path from 'path';\n\nclass OperationIdService {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.fileService = ctx.fileService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.eventEmitter = ctx.eventEmitter;\n\n        this.memoryCachedHandlersData = {};\n    }\n\n    generateId() {\n        return uuidv4();\n    }\n\n    async generateOperationId(status, blockchain, previousOperationId = null) {\n        const operationIdObject = await this.repositoryModuleManager.createOperationIdRecord({\n            status,\n        });\n        const { operationId } = operationIdObject;\n        this.emitChangeEvent(status, operationId, blockchain, previousOperationId);\n        this.logger.debug(`Generated operation id for request ${operationId}`);\n        return operationId;\n    }\n\n    async getOperationIdRecord(operationId) {\n        const operationIdRecord = await this.repositoryModuleManager.getOperationIdRecord(\n            operationId,\n        );\n        return operationIdRecord;\n    }\n\n    operationIdInRightFormat(operationId) {\n        return validate(operationId);\n    }\n\n    async updateOperationIdStatusWithValues(\n        operationId,\n        blockchain,\n        status,\n        value1 = null,\n        value2 = null,\n        value3 = null,\n        timestamp = Date.now(),\n    ) {\n        const response = {\n            status,\n            timestamp,\n        };\n\n        this.emitChangeEvent(status, operationId, blockchain, value1, value2, value3, timestamp);\n\n        await this.repositoryModuleManager.updateOperationIdRecord(response, operationId);\n    }\n\n    async updateOperationIdStatus(\n        operationId,\n        blockchain,\n        status,\n        errorMessage = null,\n        errorType = null,\n    ) {\n        const response = {\n            status,\n        };\n\n        if (errorMessage !== null) {\n            this.logger.debug(`Marking operation id ${operationId} as failed`);\n            response.data = JSON.stringify({ errorMessage, errorType });\n            await this.removeOperationIdCache(operationId);\n        }\n\n        if (errorType) {\n            this.emitChangeEvent(errorType, operationId, blockchain, errorMessage, errorType);\n        } else {\n            this.emitChangeEvent(status, operationId, blockchain, errorMessage, errorType);\n        }\n        await this.repositoryModuleManager.updateOperationIdRecord(response, operationId);\n    }\n\n    emitChangeEvent(\n        status,\n        operationId,\n        blockchainId = null,\n        value1 = null,\n        value2 = null,\n        value3 = null,\n        timestamp = Date.now(),\n    ) {\n        const eventName = 'operation_status_changed';\n\n        const eventData = {\n            lastEvent: status,\n            operationId,\n            blockchainId,\n            timestamp,\n            value1,\n            value2,\n            value3,\n        };\n\n        this.eventEmitter.emit(eventName, eventData);\n    }\n\n    async cacheOperationIdDataToMemory(operationId, data) {\n        this.logger.debug(`Caching data for operation id: ${operationId} in memory`);\n\n        this.memoryCachedHandlersData[operationId] = { data, timestamp: Date.now() };\n    }\n\n    async cacheOperationIdDataToFile(operationId, data) {\n        this.logger.debug(`Caching data for operation id: ${operationId} in file`);\n        const operationIdCachePath = this.fileService.getOperationIdCachePath();\n\n        await this.fileService.writeContentsToFile(\n            operationIdCachePath,\n            operationId,\n            JSON.stringify(data),\n        );\n    }\n\n    async getCachedOperationIdData(operationId) {\n        if (this.memoryCachedHandlersData[operationId]) {\n            this.logger.debug(`Reading operation id: ${operationId} cached data from memory`);\n            return this.memoryCachedHandlersData[operationId].data;\n        }\n\n        this.logger.debug(\n            `Didn't manage to get cached ${operationId} data from memory, trying file`,\n        );\n        const documentPath = this.fileService.getOperationIdDocumentPath(operationId);\n        let data;\n        if (await this.fileService.pathExists(documentPath)) {\n            data = await this.fileService.readFile(documentPath, true);\n        }\n        return data;\n    }\n\n    async removeOperationIdCache(operationId) {\n        this.logger.debug(`Removing operation id: ${operationId} cached data`);\n        const operationIdCachePath = this.fileService.getOperationIdDocumentPath(operationId);\n        await this.fileService.removeFile(operationIdCachePath);\n        this.removeOperationIdMemoryCache(operationId);\n    }\n\n    removeOperationIdMemoryCache(operationId) {\n        this.logger.debug(`Removing operation id: ${operationId} cached data from memory`);\n        delete this.memoryCachedHandlersData[operationId];\n    }\n\n    getOperationIdMemoryCacheSizeBytes() {\n        let total = 0;\n        for (const operationId in this.memoryCachedHandlersData) {\n            const { data } = this.memoryCachedHandlersData[operationId];\n            total += Buffer.from(JSON.stringify(data)).byteLength;\n        }\n        return total;\n    }\n\n    async getOperationIdFileCacheSizeBytes() {\n        const cacheFolderPath = this.fileService.getOperationIdCachePath();\n        const cacheFolderExists = await this.fileService.pathExists(cacheFolderPath);\n        if (!cacheFolderExists) return 0;\n\n        const fileList = await this.fileService.readDirectory(cacheFolderPath);\n        const sizeResults = await Promise.allSettled(\n            fileList.map((fileName) =>\n                this.fileService\n                    .stat(path.join(cacheFolderPath, fileName))\n                    .then((stats) => stats.size),\n            ),\n        );\n        return sizeResults\n            .filter((res) => res.status === 'fulfilled')\n            .reduce((acc, res) => acc + res.value, 0);\n    }\n\n    async removeExpiredOperationIdMemoryCache(expiredTimeout) {\n        const now = Date.now();\n        let deleted = 0;\n        for (const operationId in this.memoryCachedHandlersData) {\n            const { data, timestamp } = this.memoryCachedHandlersData[operationId];\n            if (timestamp + expiredTimeout < now) {\n                delete this.memoryCachedHandlersData[operationId];\n                deleted += Buffer.from(JSON.stringify(data)).byteLength;\n            }\n        }\n        return deleted;\n    }\n\n    async removeExpiredOperationIdFileCache(expiredTimeout, batchSize) {\n        const cacheFolderPath = this.fileService.getOperationIdCachePath();\n        const cacheFolderExists = await this.fileService.pathExists(cacheFolderPath);\n        if (!cacheFolderExists) {\n            return;\n        }\n        const fileList = await this.fileService.readDirectory(cacheFolderPath);\n\n        const now = new Date();\n        const deleteFile = async (fileName) => {\n            const filePath = path.join(cacheFolderPath, fileName);\n            const createdDate = (await this.fileService.stat(filePath)).mtime;\n            if (createdDate.getTime() + expiredTimeout < now.getTime()) {\n                await this.fileService.removeFile(filePath);\n                return true;\n            }\n            return false;\n        };\n        let totalDeleted = 0;\n        for (let i = 0; i < fileList.length; i += batchSize) {\n            const batch = fileList.slice(i, i + batchSize);\n            // eslint-disable-next-line no-await-in-loop\n            const deletionResults = await Promise.allSettled(batch.map(deleteFile));\n            totalDeleted += deletionResults.filter(\n                (result) => result.status === 'fulfilled' && result.value,\n            ).length;\n        }\n\n        return totalDeleted;\n    }\n}\n\nexport default OperationIdService;\n"
  },
  {
    "path": "src/service/operation-service.js",
    "content": "import { Mutex } from 'async-mutex';\nimport {\n    OPERATION_ID_STATUS,\n    OPERATION_REQUEST_STATUS,\n    OPERATION_STATUS,\n} from '../constants/constants.js';\n\nconst MUTEX_TTL_MS = 5 * 60 * 1000;\nconst MUTEX_SWEEP_INTERVAL_MS = 5 * 60 * 1000;\n\nclass OperationService {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.operationIdService = ctx.operationIdService;\n        this.commandExecutor = ctx.commandExecutor;\n        this._operationMutexes = new Map();\n        this._terminalOperations = new Map();\n\n        this._sweepInterval = setInterval(() => this._sweepStaleMutexes(), MUTEX_SWEEP_INTERVAL_MS);\n        if (this._sweepInterval.unref) {\n            this._sweepInterval.unref();\n        }\n    }\n\n    _getOperationMutex(operationId) {\n        if (!this._operationMutexes.has(operationId)) {\n            this._operationMutexes.set(operationId, new Mutex());\n        }\n        return this._operationMutexes.get(operationId);\n    }\n\n    _markOperationTerminal(operationId) {\n        this._terminalOperations.set(operationId, Date.now());\n    }\n\n    _isOperationTerminal(operationId) {\n        return this._terminalOperations.has(operationId);\n    }\n\n    _sweepStaleMutexes() {\n        const now = Date.now();\n        for (const [operationId, terminatedAt] of this._terminalOperations) {\n            if (now - terminatedAt >= MUTEX_TTL_MS) {\n                this._operationMutexes.delete(operationId);\n                this._terminalOperations.delete(operationId);\n            }\n        }\n    }\n\n    getOperationName() {\n        return this.operationName;\n    }\n\n    getNetworkProtocols() {\n        return this.networkProtocols;\n    }\n\n    async getOperationStatus(operationId) {\n        return this.repositoryModuleManager.getOperationStatus(\n            this.getOperationName(),\n            operationId,\n        );\n    }\n\n    async getResponsesStatuses(responseStatus, errorMessage, operationId) {\n        let responses = [];\n        const self = this;\n        const mutex = this._getOperationMutex(operationId);\n        await mutex.runExclusive(async () => {\n            if (self._isOperationTerminal(operationId)) {\n                self.logger.debug(`Skipping late response for terminal operation ${operationId}`);\n                return;\n            }\n            await self.repositoryModuleManager.createOperationResponseRecord(\n                responseStatus,\n                this.operationName,\n                operationId,\n                errorMessage,\n            );\n            responses = await self.repositoryModuleManager.getOperationResponsesStatuses(\n                this.operationName,\n                operationId,\n            );\n        });\n\n        const operationIdStatuses = {};\n        operationIdStatuses[operationId] = { failedNumber: 0, completedNumber: 0 };\n\n        for (const response of responses) {\n            if (response.status === OPERATION_REQUEST_STATUS.FAILED) {\n                operationIdStatuses[operationId].failedNumber += 1;\n            } else {\n                operationIdStatuses[operationId].completedNumber += 1;\n            }\n        }\n\n        return operationIdStatuses;\n    }\n\n    async markOperationAsCompleted(\n        operationId,\n        blockchain,\n        responseData,\n        endStatuses,\n        options = {},\n    ) {\n        this._markOperationTerminal(operationId);\n        const { reuseExistingCache = false } = options;\n        this.logger.info(`Finalizing ${this.operationName} for operationId: ${operationId}`);\n\n        await this.repositoryModuleManager.updateOperationStatus(\n            this.operationName,\n            operationId,\n            OPERATION_STATUS.COMPLETED,\n        );\n\n        if (responseData === null) {\n            await this.operationIdService.removeOperationIdCache(operationId);\n        } else {\n            await this.operationIdService.cacheOperationIdDataToMemory(operationId, responseData);\n            if (!reuseExistingCache) {\n                await this.operationIdService.cacheOperationIdDataToFile(operationId, responseData);\n            }\n        }\n\n        for (let i = 0; i < endStatuses.length; i += 1) {\n            const status = endStatuses[i];\n            const response = {\n                status,\n            };\n\n            this.operationIdService.emitChangeEvent(status, operationId, blockchain);\n            if (i === endStatuses.length - 1) {\n                // eslint-disable-next-line no-await-in-loop\n                await this.repositoryModuleManager.updateOperationIdRecord(response, operationId);\n            }\n        }\n    }\n\n    async markOperationAsFailed(operationId, blockchain, message, errorType) {\n        this._markOperationTerminal(operationId);\n        this.logger.info(`${this.operationName} for operationId: ${operationId} failed.`);\n\n        await this.operationIdService.removeOperationIdCache(operationId);\n\n        await this.repositoryModuleManager.updateOperationStatus(\n            this.operationName,\n            operationId,\n            OPERATION_STATUS.FAILED,\n        );\n\n        await this.operationIdService.updateOperationIdStatus(\n            operationId,\n            blockchain,\n            OPERATION_ID_STATUS.FAILED,\n            message,\n            errorType,\n        );\n    }\n\n    async scheduleOperationForLeftoverNodes(commandData, leftoverNodes) {\n        await this.commandExecutor.add({\n            name: `${this.operationName}ScheduleMessagesCommand`,\n            delay: 0,\n            data: { ...commandData, leftoverNodes },\n            transactional: false,\n        });\n    }\n\n    logResponsesSummary(completedNumber, failedNumber) {\n        this.logger.info(\n            `Total number of responses: ${\n                failedNumber + completedNumber\n            }, failed: ${failedNumber}, completed: ${completedNumber}`,\n        );\n    }\n\n    getBatchSize() {\n        throw Error('getBatchSize not implemented');\n    }\n\n    getMinAckResponses() {\n        throw Error('getMinAckResponses not implemented');\n    }\n}\n\nexport default OperationService;\n"
  },
  {
    "path": "src/service/paranet-service.js",
    "content": "class ParanetService {\n    constructor(ctx) {\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.ualService = ctx.ualService;\n        this.cryptoService = ctx.cryptoService;\n    }\n\n    async initializeParanetRecord(blockchain, paranetId) {\n        const paranetName = await this.blockchainModuleManager.getParanetName(\n            blockchain,\n            paranetId,\n        );\n        const paranetDescription = await this.blockchainModuleManager.getDescription(\n            blockchain,\n            paranetId,\n        );\n        if (!(await this.repositoryModuleManager.paranetExists(paranetId, blockchain))) {\n            await this.repositoryModuleManager.createParanetRecord(\n                paranetName,\n                paranetDescription,\n                paranetId,\n                blockchain,\n            );\n        }\n    }\n\n    constructParanetId(contract, knowledgeCollectionId, knowledgeAssetId) {\n        return this.cryptoService.keccak256EncodePacked(\n            ['address', 'uint256', 'uint256'],\n            [contract, knowledgeCollectionId, knowledgeAssetId],\n        );\n    }\n\n    constructKnowledgeAssetId(contract, tokenId) {\n        return this.cryptoService.keccak256EncodePacked(\n            ['address', 'uint256'],\n            [contract, tokenId],\n        );\n    }\n\n    getParanetRepositoryName(paranetUAL) {\n        if (this.ualService.isUAL(paranetUAL)) {\n            // Replace : and / with -\n            return `paranet-${paranetUAL.replace(/[/:]/g, '-').toLowerCase()}`;\n        }\n        throw new Error(\n            `Unable to get Paranet repository name. Paranet id doesn't have UAL format: ${paranetUAL}`,\n        );\n    }\n\n    getParanetIdFromUAL(paranetUAL) {\n        const { contract, knowledgeCollectionId, knowledgeAssetId } =\n            this.ualService.resolveUAL(paranetUAL);\n        return this.constructParanetId(contract, knowledgeCollectionId, knowledgeAssetId);\n    }\n}\n\nexport default ParanetService;\n"
  },
  {
    "path": "src/service/pending-storage-service.js",
    "content": "import path from 'path';\nimport {\n    NETWORK_SIGNATURES_FOLDER,\n    PUBLISHER_NODE_SIGNATURES_FOLDER,\n} from '../constants/constants.js';\n\nclass PendingStorageService {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.fileService = ctx.fileService;\n        this.repositoryModuleManager = ctx.repositoryModuleManager; // this is not used\n        this.tripleStoreService = ctx.tripleStoreService; // this is not used\n        this._merkleRootIndex = new Map();\n    }\n\n    async cacheDataset(operationId, datasetRoot, dataset, remotePeerId) {\n        this.logger.debug(\n            `Caching ${datasetRoot} dataset root, operation id: ${operationId} in file in pending storage`,\n        );\n\n        this._merkleRootIndex.set(datasetRoot, operationId);\n\n        await this.fileService.writeContentsToFile(\n            this.fileService.getPendingStorageCachePath(),\n            operationId,\n            JSON.stringify({\n                merkleRoot: datasetRoot,\n                assertion: dataset,\n                remotePeerId,\n            }),\n        );\n    }\n\n    getOperationIdByMerkleRoot(merkleRoot) {\n        return this._merkleRootIndex.get(merkleRoot) ?? null;\n    }\n\n    async getCachedDataset(operationId) {\n        this.logger.debug(`Retrieving cached dataset for ${operationId} from pending storage`);\n\n        const filePath = this.fileService.getPendingStorageDocumentPath(operationId);\n\n        try {\n            const fileContents = await this.fileService.readFile(filePath, true);\n            return fileContents.assertion;\n        } catch (error) {\n            this.logger.error(\n                `Failed to retrieve or parse cached dataset for ${operationId}: ${error.message}`,\n            );\n            throw error;\n        }\n    }\n\n    async removeExpiredFileCache(expirationTimeMillis, maxRemovalCount) {\n        this.logger.debug(\n            `Cleaning up expired files older than ${expirationTimeMillis} milliseconds. Max removal: ${maxRemovalCount}`,\n        );\n\n        const now = Date.now();\n        let removedCount = 0;\n\n        try {\n            // Define the paths to the directories we want to clean\n            const storagePaths = [\n                this.fileService.getPendingStorageCachePath(),\n                this.fileService.getSignatureStorageFolderPath(NETWORK_SIGNATURES_FOLDER),\n                this.fileService.getSignatureStorageFolderPath(PUBLISHER_NODE_SIGNATURES_FOLDER),\n            ];\n\n            const filesToDelete = [];\n\n            // Function to collect files from the provided base path\n            const collectFiles = async (basePath) => {\n                if (!(await this.fileService.pathExists(basePath))) {\n                    this.logger.warn(`Storage path does not exist: ${basePath}`);\n                    return;\n                }\n\n                const files = await this.fileService.readDirectory(basePath);\n\n                // Add all files found in the directory to the filesToDelete array\n                files.forEach((file) => {\n                    filesToDelete.push({ file, basePath });\n                });\n            };\n\n            // Collect files from both storage paths\n            for (const basePath of storagePaths) {\n                // eslint-disable-next-line no-await-in-loop\n                await collectFiles(basePath);\n            }\n\n            // Function to delete an expired file\n            const deleteFile = async ({ file, basePath }) => {\n                const filePath = path.join(basePath, file);\n                this.logger.debug(`Attempting to delete file: ${filePath}`);\n\n                try {\n                    const fileStats = await this.fileService.stat(filePath);\n                    this.logger.debug(`File stats for ${filePath}: ${JSON.stringify(fileStats)}`);\n\n                    const createdDate = fileStats.mtime;\n                    if (createdDate.getTime() + expirationTimeMillis < now) {\n                        this._removeMerkleRootIndexEntry(file);\n                        await this.fileService.removeFile(filePath);\n                        this.logger.debug(`Deleted expired file: ${filePath}`);\n                        return true;\n                    }\n                } catch (fileError) {\n                    this.logger.warn(`Failed to process file ${filePath}: ${fileError.message}`);\n                }\n                return false;\n            };\n\n            // Process files in batches\n            for (let i = 0; i < filesToDelete.length; i += maxRemovalCount) {\n                const batch = filesToDelete.slice(i, i + maxRemovalCount);\n\n                // eslint-disable-next-line no-await-in-loop\n                const deletionResults = await Promise.allSettled(batch.map(deleteFile));\n\n                removedCount += deletionResults.filter(\n                    (result) => result.status === 'fulfilled' && result.value,\n                ).length;\n\n                if (removedCount >= maxRemovalCount) {\n                    this.logger.debug(`Reached max removal count: ${maxRemovalCount}`);\n                    return removedCount;\n                }\n            }\n        } catch (error) {\n            this.logger.error(`Error during file cleanup: ${error.message}`);\n            throw error;\n        }\n\n        this.logger.debug(`Total files removed: ${removedCount}`);\n        return removedCount;\n    }\n\n    async getCachedAssertion(repository, blockchain, contract, tokenId, assertionId, operationId) {\n        const ual = this.ualService.deriveUAL(blockchain, contract, tokenId);\n\n        this.logger.debug(\n            `Reading cached assertion for ual: ${ual}, assertion id: ${assertionId}, operation id: ${operationId} from file in ${repository} pending storage`,\n        );\n        try {\n            const documentPath = await this.fileService.getPendingStorageDocumentPath(operationId);\n\n            const data = await this.fileService.readFile(documentPath, true);\n            return data;\n        } catch (error) {\n            this.logger.debug(\n                `Assertion not found in ${repository} pending storage. Error message: ${error.message}, ${error.stackTrace}`,\n            );\n            return null;\n        }\n    }\n\n    async removeCachedAssertion(repository, blockchain, contract, tokenId, operationId) {\n        const ual = this.ualService.deriveUAL(blockchain, contract, tokenId);\n\n        this.logger.debug(\n            `Removing cached assertion for ual: ${ual} operation id: ${operationId} from file in ${repository} pending storage`,\n        );\n\n        this._removeMerkleRootIndexEntry(operationId);\n\n        const pendingAssertionPath = await this.fileService.getPendingStorageDocumentPath(\n            operationId,\n        );\n        await this.fileService.removeFile(pendingAssertionPath);\n\n        const pendingStorageFolderPath = this.fileService.getParentDirectory(pendingAssertionPath);\n\n        try {\n            const otherPendingAssertions = await this.fileService.readDirectory(\n                pendingStorageFolderPath,\n            );\n            if (otherPendingAssertions.length === 0) {\n                await this.fileService.removeFolder(pendingStorageFolderPath);\n            }\n        } catch (error) {\n            this.logger.debug(\n                `Assertions folder not found in ${repository} pending storage. ` +\n                    `Error message: ${error.message}, ${error.stackTrace}`,\n            );\n        }\n    }\n\n    _removeMerkleRootIndexEntry(operationId) {\n        for (const [root, opId] of this._merkleRootIndex) {\n            if (opId === operationId) {\n                this._merkleRootIndex.delete(root);\n                break;\n            }\n        }\n    }\n\n    async getPendingState(operationId) {\n        return this.fileService.getPendingStorageLatestDocument(operationId);\n    }\n}\n\nexport default PendingStorageService;\n"
  },
  {
    "path": "src/service/proofing-service.js",
    "content": "import { kcTools } from 'assertion-tools';\nimport { setTimeout } from 'timers/promises';\nimport {\n    PROOFING_INTERVAL,\n    REORG_PROOFING_BUFFER,\n    PRIVATE_HASH_SUBJECT_PREFIX,\n    CHUNK_SIZE,\n    OPERATION_ID_STATUS,\n    TRIPLES_VISIBILITY,\n    PROOFING_MAX_ATTEMPTS,\n} from '../constants/constants.js';\n\nclass ProofingService {\n    constructor(ctx) {\n        this.ctx = ctx;\n        this.logger = ctx.logger;\n        this.ualService = ctx.ualService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.validationService = ctx.validationService;\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n    }\n\n    async initialize() {\n        this.logger.info('[PROOFING] Initializing ProofingService');\n        const promises = [];\n        for (const blockchainId of this.blockchainModuleManager.getImplementationNames()) {\n            this.logger.info(\n                `[PROOFING] Initializing proofing service for blockchain ${blockchainId}`,\n            );\n            promises.push(this.proofingMechanism(blockchainId));\n        }\n        await Promise.all(promises);\n        this.logger.info('[PROOFING] ProofingService initialization completed');\n    }\n\n    async proofingMechanism(blockchainId) {\n        this.logger.debug(\n            `[PROOFING] Setting up proofing mechanism for blockchain ${blockchainId}`,\n        );\n        // Flag to track if mechanism is running\n        let isRunning = false;\n\n        // Set up interval\n        const interval = setInterval(async () => {\n            // Skip if already running\n            if (isRunning) {\n                this.logger.debug(\n                    `[PROOFING] Proofing mechanism for ${blockchainId} still running, skipping this interval`,\n                );\n                return;\n            }\n\n            try {\n                isRunning = true;\n                this.logger.debug(\n                    `[PROOFING] Starting proofing cycle for blockchain ${blockchainId}`,\n                );\n\n                // Proofing logic\n                await this.runProofing(blockchainId);\n                this.logger.debug(\n                    `[PROOFING] Completed proofing cycle for blockchain ${blockchainId}`,\n                );\n            } catch (error) {\n                this.logger.error(\n                    `[PROOFING] Error in proofing mechanism for ${blockchainId}: ${error.message}, stack: ${error.stack}`,\n                );\n            } finally {\n                isRunning = false;\n            }\n        }, PROOFING_INTERVAL);\n\n        // Store interval reference for cleanup\n        this[`${blockchainId}Interval`] = interval;\n        this.logger.info(\n            `[PROOFING] Proofing mechanism initialized for blockchain ${blockchainId}`,\n        );\n    }\n\n    async runProofing(blockchainId) {\n        this.logger.debug(`[PROOFING] Running proofing mechanism for ${blockchainId}`);\n\n        const peerId = this.networkModuleManager.getPeerId().toB58String();\n        const isNodePartOfShard = await this.repositoryModuleManager.isNodePartOfShard(\n            blockchainId,\n            peerId,\n        );\n        if (!isNodePartOfShard) {\n            this.logger.debug(\n                `[PROOFING] Skipping proofing. Node is not part of shard for blockchain: ${blockchainId}, peerId: ${peerId}`,\n            );\n            return;\n        }\n\n        const identityId = await this.blockchainModuleManager.getIdentityId(blockchainId);\n        // Check what is current proof period {isValid, activeProofPeriodStartBlock}\n        const activeProofPeriodStatus =\n            await this.blockchainModuleManager.getActiveProofPeriodStatus(blockchainId);\n        const latestChallenge =\n            await this.repositoryModuleManager.getLatestRandomSamplingChallengeRecordForBlockchainId(\n                blockchainId,\n            );\n\n        this.logger.debug(\n            `[PROOFING] Checking proof period validity: isValid=${activeProofPeriodStatus.isValid}, activeProofPeriodStartBlock=${activeProofPeriodStatus.activeProofPeriodStartBlock}, latestChallengeBlock=${latestChallenge?.activeProofPeriodStartBlock}, sentSuccessfully=${latestChallenge?.sentSuccessfully}, blockchainId=${blockchainId}`,\n        );\n\n        if (\n            activeProofPeriodStatus.isValid &&\n            latestChallenge?.activeProofPeriodStartBlock ===\n                activeProofPeriodStatus.activeProofPeriodStartBlock.toNumber()\n        ) {\n            if (latestChallenge.sentSuccessfully) {\n                if (!latestChallenge.finalized) {\n                    this.logger.debug(\n                        `[PROOFING] Processing non-finalized challenge for blockchain: ${blockchainId}`,\n                    );\n\n                    // We have latest challenge and we sent valid proof\n                    // Check onchain if it has score\n                    const score = await this.blockchainModuleManager.getNodeEpochProofPeriodScore(\n                        blockchainId,\n                        identityId,\n                        latestChallenge.epoch,\n                        latestChallenge.activeProofPeriodStartBlock,\n                    );\n                    this.logger.debug(\n                        `[PROOFING] Retrieved node score for blockchain: ${blockchainId}, identityId: ${identityId}, score: ${score.toString()}`,\n                    );\n\n                    // If score is greater than 0 than proof was sent and was valid\n                    // Ensure no reorgs happened by checking if it has score and enough time has passed and if possible mark it as finalized\n                    if (score.gt(0)) {\n                        // Sent more than minute ago check onchain confirm it finalized and it's good\n                        if (\n                            latestChallenge.updatedAt.getTime() + REORG_PROOFING_BUFFER <=\n                            Date.now()\n                        ) {\n                            this.logger.info(\n                                `[PROOFING] Finalizing challenge for blockchainId: ${blockchainId}, challengeId: ${latestChallenge.id}`,\n                            );\n                            latestChallenge.finalized = true;\n                            await this.repositoryModuleManager.setCompletedAndFinalizedRandomSamplingChallengeRecord(\n                                latestChallenge.id,\n                                true,\n                                true,\n                            );\n                            this.operationIdService.emitChangeEvent(\n                                'PROOF_CHALANGE_FINALIZED',\n                                this.generateOperationId(\n                                    blockchainId,\n                                    latestChallenge.epoch,\n                                    latestChallenge.activeProofPeriodStartBlock,\n                                ),\n                                blockchainId,\n                                latestChallenge.epoch,\n                                latestChallenge.activeProofPeriodStartBlock,\n                            );\n                        } else {\n                            this.logger.info(\n                                `[PROOFING] Waiting for reorg buffer to pass before finalizing for blockchain: ${blockchainId}, challengeId: ${latestChallenge.id}`,\n                            );\n                        }\n                    } else {\n                        this.logger.warn(\n                            `[PROOFING] Zero score detected, resetting challenge status for blockchain: ${blockchainId}, challengeId: ${latestChallenge.id}`,\n                        );\n                        latestChallenge.sentSuccessfully = false;\n                        latestChallenge.finalized = false;\n                        await this.repositoryModuleManager.setCompletedAndFinalizedRandomSamplingChallengeRecord(\n                            latestChallenge.id,\n                            latestChallenge.sentSuccessfully,\n                            latestChallenge.finalized,\n                        );\n                        await this.prepareAndSendProof(blockchainId, identityId);\n                    }\n                }\n            } else {\n                const ual = this.ualService.deriveUAL(\n                    blockchainId,\n                    latestChallenge.contractAddress,\n                    latestChallenge.knowledgeCollectionId,\n                );\n\n                const data = await this.fetchAndProcessAssertion(blockchainId, ual);\n\n                this.operationIdService.emitChangeEvent(\n                    'PROOF_ASSERTION_FETCHED',\n                    this.generateOperationId(\n                        blockchainId,\n                        latestChallenge.epoch,\n                        latestChallenge.activeProofPeriodStartBlock,\n                    ),\n                    blockchainId,\n                    latestChallenge.epoch,\n                    latestChallenge.activeProofPeriodStartBlock,\n                );\n\n                if (data.public.length === 0) {\n                    this.logger.warn(\n                        `[PROOFING] No assertions found for blockchain: ${blockchainId}, challengeId: ${latestChallenge.id}, ual: ${ual}`,\n                    );\n                    return;\n                }\n\n                const proof = await this.calculateAndSubmitProof(\n                    data,\n                    latestChallenge,\n                    blockchainId,\n                );\n                this.logger.info(\n                    `[PROOFING] Proof calculated and submitted successfully for blockchain: ${blockchainId}, challengeId: ${latestChallenge.id}`,\n                );\n\n                return proof;\n            }\n            // If finalized is do nothing, wait for next proof\n        } else {\n            this.logger.info(`[PROOFING] Preparing new proof for blockchain: ${blockchainId}`);\n            // Node needs to get new challenge or Node sent wrong proof\n            await this.prepareAndSendProof(blockchainId, identityId);\n        }\n    }\n\n    async prepareAndSendProof(blockchainId, identityId) {\n        this.logger.debug(`[PROOFING] Starting proof preparation for blockchain: ${blockchainId}`);\n\n        try {\n            const newChallenge = await this.getAndPersistNewChallenge(blockchainId, identityId);\n\n            const ual = this.ualService.deriveUAL(\n                blockchainId,\n                newChallenge.contractAddress,\n                newChallenge.knowledgeCollectionId,\n            );\n\n            this.logger.debug(\n                `[PROOFING] New challenge created: challengeId=${newChallenge.id}, epoch=${newChallenge.epoch}, contractAddress=${newChallenge.contractAddress}, knowledgeCollectionId=${newChallenge.knowledgeCollectionId}`,\n            );\n\n            const data = await this.fetchAndProcessAssertion(blockchainId, ual);\n\n            this.operationIdService.emitChangeEvent(\n                'PROOF_ASSERTION_FETCHED',\n                this.generateOperationId(\n                    blockchainId,\n                    newChallenge.epoch,\n                    newChallenge.activeProofPeriodStartBlock,\n                ),\n                blockchainId,\n                newChallenge.epoch,\n                newChallenge.activeProofPeriodStartBlock,\n            );\n\n            if (data.public.length === 0) {\n                throw new Error(\n                    `[PROOFING] No assertions found for blockchain: ${blockchainId}, ual: ${ual}`,\n                );\n            }\n\n            const proof = await this.calculateAndSubmitProof(data, newChallenge, blockchainId);\n            this.logger.info(\n                `[PROOFING] Proof calculated and submitted successfully for blockchain: ${blockchainId}, challengeId: ${newChallenge.id}`,\n            );\n\n            return proof;\n        } catch (error) {\n            this.logger.error(\n                `[PROOFING] Failed to prepare and send proof for blockchain: ${blockchainId}. Error: ${error.message}, stack: ${error.stack}`,\n            );\n            throw error;\n        }\n    }\n\n    async getAndPersistNewChallenge(blockchainId, identityId) {\n        // Node has challenge for previous period need to get new one\n        // Get new challenge\n        const createChallengeResult = await this.blockchainModuleManager.createChallenge(\n            blockchainId,\n        );\n\n        if (\n            !createChallengeResult.success &&\n            !createChallengeResult?.error?.message?.includes(\n                'An unsolved challenge already exists for this node in the current proof period',\n            )\n        ) {\n            // Throw an error only if it's not the expected \"already exists\" error\n            throw new Error(createChallengeResult.error);\n        }\n\n        const newChallenge = await this.blockchainModuleManager.getNodeChallenge(\n            blockchainId,\n            identityId,\n        );\n\n        if (createChallengeResult.success) {\n            // Only emit the event if a new challenge was actually generated\n            this.operationIdService.emitChangeEvent(\n                'PROOF_NEW_CHALANGE_GENERATED',\n                this.generateOperationId(\n                    blockchainId,\n                    newChallenge.epoch.toNumber(),\n                    newChallenge.activeProofPeriodStartBlock.toNumber(),\n                ),\n                blockchainId,\n                newChallenge.epoch.toNumber(),\n                newChallenge.activeProofPeriodStartBlock.toNumber(),\n            );\n        }\n\n        const newChallengeRecord = {\n            blockchainId,\n            epoch: newChallenge.epoch.toNumber(),\n            activeProofPeriodStartBlock: newChallenge.activeProofPeriodStartBlock.toNumber(),\n            contractAddress: newChallenge.knowledgeCollectionStorageContract.toLowerCase(),\n            knowledgeCollectionId: newChallenge.knowledgeCollectionId.toNumber(),\n            chunkNumber: newChallenge.chunkId.toNumber(),\n            sentSuccessfully: false,\n            finalized: false,\n        };\n        const newRecord = await this.repositoryModuleManager.createRandomSamplingChallengeRecord(\n            newChallengeRecord,\n        );\n        this.operationIdService.emitChangeEvent(\n            'PROOF_NEW_CHALANGE_PERSISTED',\n            this.generateOperationId(\n                blockchainId,\n                newChallenge.epoch.toNumber(),\n                newChallenge.activeProofPeriodStartBlock.toNumber(),\n            ),\n            blockchainId,\n            newChallenge.epoch.toNumber(),\n            newChallenge.activeProofPeriodStartBlock.toNumber(),\n        );\n        return newRecord;\n    }\n\n    async fetchAndProcessAssertion(blockchainId, ual) {\n        let attempt = 0;\n        let getResult;\n        const getOperationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.GET.GET_START,\n        );\n        this.operationIdService.emitChangeEvent(\n            'PROOFING_GET_STARTED',\n            getOperationId,\n            blockchainId,\n        );\n        this.logger.debug(\n            `[PROOFING] Proofing GET started for blockchain: ${blockchainId}, operationId: ${getOperationId}`,\n        );\n\n        const { contract, knowledgeCollectionId } = this.ualService.resolveUAL(ual);\n        await this.commandExecutor.add({\n            name: 'getCommand',\n            sequence: [],\n            delay: 0,\n            data: {\n                operationId: getOperationId,\n                blockchain: blockchainId,\n                contract,\n                knowledgeCollectionId,\n                state: 0,\n                ual,\n                contentType: TRIPLES_VISIBILITY.PUBLIC,\n            },\n            transactional: false,\n        });\n\n        do {\n            // eslint-disable-next-line no-await-in-loop\n            await setTimeout(500);\n            // eslint-disable-next-line no-await-in-loop\n            getResult = await this.operationIdService.getOperationIdRecord(getOperationId);\n            attempt += 1;\n        } while (\n            attempt < PROOFING_MAX_ATTEMPTS &&\n            getResult?.status !== OPERATION_ID_STATUS.FAILED &&\n            getResult?.status !== OPERATION_ID_STATUS.COMPLETED\n        );\n\n        if (getResult?.status !== OPERATION_ID_STATUS.COMPLETED) {\n            // We need to stop here and retry later\n            throw new Error(\n                `[PROOFING] Unable to Proofing GET Knowledge Collection for proof Id: ${knowledgeCollectionId}, for contract: ${contract}, blockchain: ${blockchainId}, GET result: ${JSON.stringify(\n                    getResult,\n                )}`,\n            );\n        }\n\n        const { assertion } = await this.operationIdService.getCachedOperationIdData(\n            getOperationId,\n        );\n\n        this.logger.debug(\n            `[PROOFING] Proofing GET: ${assertion.public.length} nquads found for asset with ual: ${ual}`,\n        );\n\n        return assertion;\n    }\n\n    async calculateAndSubmitProof(data, challenge, blockchainId) {\n        const publicAssertion = data.public;\n\n        const filteredPublic = [];\n        const privateHashTriples = [];\n        publicAssertion.forEach((triple) => {\n            if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                privateHashTriples.push(triple);\n            } else {\n                filteredPublic.push(triple);\n            }\n        });\n\n        let publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n            filteredPublic,\n            true,\n        );\n        publicKnowledgeAssetsTriplesGrouped.push(\n            ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n        );\n\n        publicKnowledgeAssetsTriplesGrouped = publicKnowledgeAssetsTriplesGrouped\n            .map((t) => t.sort())\n            .flat();\n\n        // Calculate proof\n        const proof = kcTools.calculateMerkleProof(\n            publicKnowledgeAssetsTriplesGrouped,\n            CHUNK_SIZE,\n            challenge.chunkNumber,\n        );\n        // Submit proof\n        // How to validate result? (we do it in next iteration)\n        const chunks = kcTools.splitIntoChunks(publicKnowledgeAssetsTriplesGrouped);\n        const chunk = chunks[challenge.chunkNumber];\n        await this.blockchainModuleManager.submitProof(blockchainId, chunk, proof.proof);\n        this.operationIdService.emitChangeEvent(\n            'PROOF_SUBMITTED',\n            this.generateOperationId(\n                blockchainId,\n                challenge.epoch,\n                challenge.activeProofPeriodStartBlock,\n            ),\n            blockchainId,\n            null,\n            null,\n        );\n        const score = await this.blockchainModuleManager.getNodeEpochProofPeriodScore(\n            blockchainId,\n            await this.blockchainModuleManager.getIdentityId(blockchainId),\n            challenge.epoch,\n            challenge.activeProofPeriodStartBlock,\n        );\n\n        if (score.gt(0)) {\n            // Move score persistence to finalization\n            await this.repositoryModuleManager.setCompletedAndScoreRandomSamplingChallengeRecord(\n                challenge.id,\n                true,\n                BigInt(score.toString()), // eslint-disable-line no-undef\n            );\n            this.operationIdService.emitChangeEvent(\n                'PROOF_SUBMITTED_SUCCESSFULLY',\n                this.generateOperationId(\n                    blockchainId,\n                    challenge.epoch,\n                    challenge.activeProofPeriodStartBlock,\n                ),\n                blockchainId,\n                null,\n                null,\n            );\n        }\n\n        return proof;\n    }\n\n    generateOperationId(blockchainId, epoch, activeProofPeriodStartBlock) {\n        return `${blockchainId}-${epoch}-${activeProofPeriodStartBlock}`;\n    }\n\n    // Add cleanup method to stop intervals\n    cleanup() {\n        this.logger.info('[PROOFING] Starting ProofingService cleanup');\n        for (const blockchainId of this.blockchainModuleManager.getImplementationNames()) {\n            const intervalKey = `${blockchainId}Interval`;\n            if (this[intervalKey]) {\n                this.logger.debug(`Clearing interval for blockchain ${blockchainId}`);\n                clearInterval(this[intervalKey]);\n                this[intervalKey] = null;\n            }\n        }\n        this.logger.info('[PROOFING] ProofingService cleanup completed');\n    }\n}\n\nexport default ProofingService;\n"
  },
  {
    "path": "src/service/protocol-service.js",
    "content": "import { NETWORK_PROTOCOLS } from '../constants/constants.js';\n\nclass ProtocolService {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n    }\n\n    toAwilixVersion(protocol) {\n        const { version } = this.resolveProtocol(protocol);\n        return `v${version.split('.').join('_')}`;\n    }\n\n    resolveProtocol(protocol) {\n        const [, name, version] = protocol.split('/');\n        return { name, version };\n    }\n\n    getProtocols() {\n        return Object.values(NETWORK_PROTOCOLS);\n    }\n\n    toOperation(protocol) {\n        const { name } = this.resolveProtocol(protocol);\n        switch (name) {\n            case 'store':\n                return 'publish';\n            case 'batch-get':\n                return 'batchGet';\n            default:\n                return name;\n        }\n    }\n\n    getReceiverCommandSequence(protocol) {\n        const version = this.toAwilixVersion(protocol);\n        const { name } = this.resolveProtocol(protocol);\n        const capitalizedOperation = name.charAt(0).toUpperCase() + name.slice(1);\n\n        const prefix = `${version}Handle${capitalizedOperation}`;\n\n        return [`${prefix}RequestCommand`];\n    }\n\n    getSenderCommandSequence(protocol) {\n        const version = this.toAwilixVersion(protocol);\n        const operation = this.toOperation(protocol);\n        const capitalizedOperation = operation.charAt(0).toUpperCase() + operation.slice(1);\n\n        const prefix = `${version}${capitalizedOperation}`;\n\n        return [`${prefix}RequestCommand`];\n    }\n}\n\nexport default ProtocolService;\n"
  },
  {
    "path": "src/service/publish-service.js",
    "content": "import OperationService from './operation-service.js';\n\nimport {\n    OPERATION_ID_STATUS,\n    NETWORK_PROTOCOLS,\n    ERROR_TYPE,\n    OPERATIONS,\n    PUBLISH_BATCH_SIZE,\n    PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS,\n} from '../constants/constants.js';\n\nclass PublishService extends OperationService {\n    constructor(ctx) {\n        super(ctx);\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n\n        this.operationName = OPERATIONS.PUBLISH;\n        this.networkProtocols = NETWORK_PROTOCOLS.STORE;\n        this.errorType = ERROR_TYPE.PUBLISH.PUBLISH_ERROR;\n        this.completedStatuses = [\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_REPLICATE_END,\n            OPERATION_ID_STATUS.PUBLISH.PUBLISH_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ];\n    }\n\n    async processResponse(command, responseStatus, responseData, errorMessage = null) {\n        const {\n            operationId,\n            blockchain,\n            numberOfFoundNodes,\n            batchSize,\n            minAckResponses,\n            datasetRoot,\n        } = command.data;\n\n        const datasetRootStatus = await this.getResponsesStatuses(\n            responseStatus,\n            errorMessage,\n            operationId,\n        );\n\n        const { completedNumber, failedNumber } = datasetRootStatus[operationId];\n\n        const totalResponses = completedNumber + failedNumber;\n\n        this.logger.debug(\n            `Processing ${\n                this.operationName\n            } response with status: ${responseStatus} for operationId: ${operationId}, dataset root: ${datasetRoot}. Total number of nodes: ${numberOfFoundNodes}, number of nodes in batch: ${Math.min(\n                numberOfFoundNodes,\n                batchSize,\n            )}, number of responses: ${totalResponses}, Completed: ${completedNumber}, Failed: ${failedNumber}, minimum replication factor: ${minAckResponses}`,\n        );\n        if (responseData.errorMessage) {\n            this.logger.trace(\n                `Error message for operation id: ${operationId}, dataset root: ${datasetRoot} : ${responseData.errorMessage}`,\n            );\n        }\n\n        // 1. Check minimum replication reached\n        // if (completedNumber === minAckResponses) {\n        //     this.logger.debug(\n        //         `Minimum replication ${minAckResponses} reached for operationId: ${operationId}, dataset root: ${datasetRoot}`,\n        //     );\n        //     await this.repositoryModuleManager.updateMinAcksReached(operationId, true);\n        // }\n\n        // 2. Check if all responses have been received\n        // 2.1 If minimum replication is reached, mark the operation as completed\n\n        const record = await this.operationIdService.getOperationIdRecord(operationId);\n        if (record?.minAcksReached) return;\n\n        if (completedNumber >= minAckResponses) {\n            this.logger.info(\n                `[PUBLISH] Minimum replication reached for operationId: ${operationId}, ` +\n                    `datasetRoot: ${datasetRoot}, completed: ${completedNumber}/${minAckResponses}`,\n            );\n            await this.repositoryModuleManager.updateMinAcksReached(operationId, true);\n            const cachedData =\n                (await this.operationIdService.getCachedOperationIdData(operationId)) || null;\n            await this.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                cachedData,\n                this.completedStatuses,\n                { reuseExistingCache: true },\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        }\n        // 2.2 Otherwise, mark as failed\n        else if (totalResponses === numberOfFoundNodes) {\n            this.logger.warn(\n                `[PUBLISH] Failed for operationId: ${operationId}, ` +\n                    `only ${completedNumber}/${minAckResponses} nodes responded successfully`,\n            );\n            await this.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Not replicated to enough nodes!',\n                this.errorType,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.PUBLISH.PUBLISH_FAILED,\n                operationId,\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        }\n        //  else {\n        //     // 3. Not all responses have arrived yet.\n        //     const potentialCompletedNumber = completedNumber + leftoverNodes.length;\n        //     const canStillReachMinReplication = potentialCompletedNumber >= minAckResponses;\n        //     const canScheduleBatch = (totalResponses - 1) % batchSize === 0;\n\n        //     // 3.1 Check if minimum replication can still be achieve  by scheduling leftover nodes\n        //     //     (and it's at the end of a batch)\n        //     // if (leftoverNodes.length > 0 && canStillReachMinReplication && canScheduleBatch) {\n        //     //     await this.scheduleOperationForLeftoverNodes(command.data, leftoverNodes);\n        //     // }\n        //     // 3.2 If minimum replication cannot be reached and it's end of a batch, mark as failed\n        //     if (!canStillReachMinReplication && canScheduleBatch) {\n        //         await this.markOperationAsFailed(\n        //             operationId,\n        //             blockchain,\n        //             'Not replicated to enough nodes!',\n        //             this.errorType,\n        //         );\n        //         this.logResponsesSummary(completedNumber, failedNumber);\n        //     }\n        // }\n    }\n\n    getBatchSize(batchSize = null) {\n        return batchSize ?? PUBLISH_BATCH_SIZE;\n    }\n\n    getMinAckResponses(minimumNumberOfNodeReplications = null) {\n        return minimumNumberOfNodeReplications ?? PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS;\n    }\n}\n\nexport default PublishService;\n"
  },
  {
    "path": "src/service/sharding-table-service.js",
    "content": "import { BYTES_IN_KILOBYTE, PEER_RECORD_UPDATE_DELAY } from '../constants/constants.js';\n\nclass ShardingTableService {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.networkModuleManager = ctx.networkModuleManager;\n        this.cryptoService = ctx.cryptoService;\n\n        this.memoryCachedPeerIds = {};\n    }\n\n    async initialize() {\n        await this.networkModuleManager.onPeerConnected((connection) => {\n            this.updatePeerRecordLastSeenAndLastDialed(connection.remotePeer.toB58String()).catch(\n                (error) => {\n                    this.logger.warn(`Unable to update connected peer, error: ${error.message}`);\n                },\n            );\n        });\n    }\n\n    async pullBlockchainShardingTable(blockchainId, transaction = null) {\n        const options = transaction ? { transaction } : {};\n\n        this.logger.debug(\n            `Removing nodes from local sharding table for blockchain ${blockchainId}.`,\n        );\n        await this.repositoryModuleManager.removeShardingTablePeerRecords(blockchainId, options);\n\n        const shardingTableLength = await this.blockchainModuleManager.getShardingTableLength(\n            blockchainId,\n        );\n        let startingIdentityId = await this.blockchainModuleManager.getShardingTableHead(\n            blockchainId,\n        );\n        const pageSize = 10;\n        const shardingTable = [];\n\n        this.logger.debug(\n            `Started pulling ${shardingTableLength} nodes from blockchain sharding table.`,\n        );\n\n        let sliceIndex = 0;\n        while (shardingTable.length < shardingTableLength) {\n            // eslint-disable-next-line no-await-in-loop\n            const nodes = await this.blockchainModuleManager.getShardingTablePage(\n                blockchainId,\n                startingIdentityId,\n                pageSize,\n            );\n            shardingTable.push(...nodes.slice(sliceIndex).filter((node) => node.nodeId !== '0x'));\n            sliceIndex = 1;\n            startingIdentityId = nodes[nodes.length - 1].identityId;\n        }\n\n        this.logger.debug(\n            `Finished pulling ${shardingTable.length} nodes from blockchain sharding table.`,\n        );\n\n        const newPeerRecords = await Promise.all(\n            shardingTable.map(async (peer) => {\n                const nodeId = this.cryptoService.convertHexToAscii(peer.nodeId);\n                const sha256 = await this.cryptoService.sha256(nodeId);\n\n                return {\n                    peerId: nodeId,\n                    blockchainId,\n                    ask: this.cryptoService.convertFromWei(peer.ask, 'ether'),\n                    stake: this.cryptoService.convertFromWei(peer.stake, 'ether'),\n                    sha256,\n                };\n            }),\n        );\n\n        await this.repositoryModuleManager.createManyPeerRecords(newPeerRecords, options);\n    }\n\n    async findShard(blockchainId, filterInactive = false) {\n        let peers = await this.repositoryModuleManager.getAllPeerRecords(\n            blockchainId,\n            filterInactive,\n        );\n        peers = peers.map((peer, index) => ({ ...peer.dataValues, index }));\n        return peers;\n    }\n\n    async isNodePartOfShard(blockchainId, peerId) {\n        return this.repositoryModuleManager.isNodePartOfShard(blockchainId, peerId);\n    }\n\n    // TODO: Remove this\n    calculateBidSuggestion(askOffset, sorted, blockchainId, kbSize, epochsNumber, r0) {\n        const effectiveAskOffset = Math.min(askOffset, sorted.length - 1);\n        const { ask } = sorted[effectiveAskOffset];\n\n        const bidSuggestion = this.cryptoService\n            .convertToWei(ask)\n            .mul(kbSize)\n            .mul(epochsNumber)\n            .mul(r0)\n            .div(BYTES_IN_KILOBYTE);\n        return bidSuggestion.toString();\n    }\n\n    async findEligibleNodes(neighbourhood, bid, r1, r0) {\n        return neighbourhood.filter((node) => node.ask <= bid / r0).slice(0, r1);\n    }\n\n    async dial(peerId) {\n        try {\n            const { addresses } = await this.findPeerAddressAndProtocols(peerId);\n            if (addresses.length) {\n                if (peerId !== this.networkModuleManager.getPeerId().toB58String()) {\n                    this.logger.trace(`Dialing peer ${peerId}.`);\n                    await this.networkModuleManager.dial(peerId);\n                }\n                await this.updatePeerRecordLastSeenAndLastDialed(peerId);\n            } else {\n                await this.updatePeerRecordLastDialed(peerId);\n            }\n        } catch (error) {\n            this.logger.trace(`Unable to dial peer ${peerId}. Error: ${error.message}`);\n            await this.updatePeerRecordLastDialed(peerId);\n        }\n    }\n\n    async updatePeerRecordLastSeenAndLastDialed(peerId) {\n        const now = Date.now();\n        const timestampThreshold = now - PEER_RECORD_UPDATE_DELAY;\n\n        if (!this.memoryCachedPeerIds[peerId]) {\n            this.memoryCachedPeerIds[peerId] = {\n                lastDialed: 0,\n                lastSeen: 0,\n            };\n        }\n        if (\n            this.memoryCachedPeerIds[peerId].lastSeen < timestampThreshold ||\n            this.memoryCachedPeerIds[peerId].lastDialed < timestampThreshold\n        ) {\n            const [rowsUpdated] =\n                await this.repositoryModuleManager.updatePeerRecordLastSeenAndLastDialed(\n                    peerId,\n                    now,\n                );\n            if (rowsUpdated) {\n                this.memoryCachedPeerIds[peerId].lastDialed = now;\n                this.memoryCachedPeerIds[peerId].lastSeen = now;\n            }\n        }\n    }\n\n    async updatePeerRecordLastDialed(peerId) {\n        const now = Date.now();\n        const timestampThreshold = now - PEER_RECORD_UPDATE_DELAY;\n        if (!this.memoryCachedPeerIds[peerId]) {\n            this.memoryCachedPeerIds[peerId] = {\n                lastDialed: 0,\n                lastSeen: 0,\n            };\n        }\n        if (this.memoryCachedPeerIds[peerId].lastDialed < timestampThreshold) {\n            const [rowsUpdated] = await this.repositoryModuleManager.updatePeerRecordLastDialed(\n                peerId,\n                now,\n            );\n            if (rowsUpdated) {\n                this.memoryCachedPeerIds[peerId].lastDialed = now;\n            }\n        }\n    }\n\n    async findPeerAddressAndProtocols(peerId) {\n        this.logger.trace(`Searching for peer ${peerId} multiaddresses in peer store.`);\n        let peerInfo = await this.networkModuleManager.getPeerInfo(peerId);\n        if (\n            !peerInfo?.addresses?.length &&\n            peerId !== this.networkModuleManager.getPeerId().toB58String()\n        ) {\n            try {\n                this.logger.trace(`Searching for peer ${peerId} multiaddresses on the network.`);\n                await this.networkModuleManager.findPeer(peerId);\n                peerInfo = await this.networkModuleManager.getPeerInfo(peerId);\n            } catch (error) {\n                this.logger.trace(`Unable to find peer ${peerId}. Error: ${error.message}`);\n            }\n        }\n\n        return {\n            id: peerId,\n            addresses: peerInfo?.addresses ?? [],\n            protocols: peerInfo?.protocols ?? [],\n        };\n    }\n}\n\nexport default ShardingTableService;\n"
  },
  {
    "path": "src/service/signature-service.js",
    "content": "class SignatureService {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n\n        this.cryptoService = ctx.cryptoService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.fileService = ctx.fileService;\n    }\n\n    async signMessage(blockchain, messageHash) {\n        const flatSignature = await this.blockchainModuleManager.signMessage(\n            blockchain,\n            messageHash,\n        );\n        const { v, r, s, _vs } = this.cryptoService.splitSignature(flatSignature);\n        return { v, r, s, vs: _vs };\n    }\n\n    async addSignatureToStorage(folderName, operationId, identityId, v, r, s, vs) {\n        await this.fileService.appendContentsToFile(\n            this.fileService.getSignatureStorageFolderPath(folderName),\n            operationId,\n            `${JSON.stringify({ identityId, v, r, s, vs })}\\n`,\n        );\n    }\n\n    async getSignaturesFromStorage(folderName, operationId) {\n        const signatureStorageFile = this.fileService.getSignatureStorageDocumentPath(\n            folderName,\n            operationId,\n        );\n\n        const rawSignatures = await this.fileService.readFile(signatureStorageFile);\n        const signaturesArray = [];\n        for (const line of rawSignatures.split('\\n')) {\n            const trimmedLine = line.trim();\n            if (trimmedLine) {\n                signaturesArray.push(JSON.parse(trimmedLine));\n            }\n        }\n\n        return signaturesArray;\n    }\n}\n\nexport default SignatureService;\n"
  },
  {
    "path": "src/service/sync-service.js",
    "content": "import { v4 as uuidv4 } from 'uuid';\nimport { setTimeout } from 'timers/promises';\nimport {\n    SYNC_INTERVAL,\n    OPERATION_ID_STATUS,\n    DKG_METADATA_PREDICATES,\n    TRIPLE_STORE_REPOSITORY,\n    BATCH_GET_UAL_MAX_LIMIT,\n    SYNC_BATCH_GET_MAX_ATTEMPTS,\n    SYNC_BATCH_GET_WAIT_TIME,\n} from '../constants/constants.js';\n\nclass SyncService {\n    // TODO: Send getter for Neuroweb fixed on last finalised block, there should be ethers flag\n    constructor(ctx) {\n        this.ctx = ctx;\n        this.syncConfig = ctx.config.assetSync.syncDKG;\n        this.logger = ctx.logger;\n        this.ualService = ctx.ualService;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n        this.tripleStoreService = ctx.tripleStoreService;\n        this.validationService = ctx.validationService;\n        this.commandExecutor = ctx.commandExecutor;\n        this.operationIdService = ctx.operationIdService;\n        this.syncStatus = {};\n        this.registeredIntervals = [];\n    }\n\n    async initialize() {\n        if (!this.syncConfig.enabled) {\n            this.logger.info('[DKG SYNC] SyncService disabled');\n            return;\n        }\n\n        this.logger.info('[DKG SYNC] Initializing SyncService');\n        this.syncBatchSize = this.syncConfig.syncBatchSize;\n        const blockchainIds = this.blockchainModuleManager.getImplementationNames();\n        const promises = await Promise.all(\n            blockchainIds.map(async (blockchainId) => {\n                this.logger.info(\n                    `[DKG SYNC] Initializing sync service for blockchain ${blockchainId}`,\n                );\n\n                // Check if operationalDB has all contract present in hub\n                const contracts =\n                    await this.blockchainModuleManager.getAssetStorageContractsAddress(\n                        blockchainId,\n                    );\n                const dbContracts = await this.repositoryModuleManager.getKCStorageContracts(\n                    blockchainId,\n                );\n\n                const missingContracts = contracts.filter(\n                    (contract) =>\n                        !dbContracts.some(\n                            (dbContract) => dbContract.toJSON().contract_address === contract,\n                        ),\n                );\n\n                if (missingContracts.length > 0) {\n                    this.logger.info(\n                        `[DKG SYNC] Adding missing contracts for blockchain ${blockchainId}: ${missingContracts.join(\n                            ', ',\n                        )}`,\n                    );\n                    await this.repositoryModuleManager.addSyncContracts(\n                        blockchainId,\n                        missingContracts,\n                    );\n                }\n\n                return this.syncMechanism(blockchainId);\n            }),\n        );\n\n        await Promise.all(promises);\n        this.logger.info('[DKG SYNC] SyncService initialization completed');\n    }\n\n    // Weirdly named, why not start mechanism?\n    async syncMechanism(blockchainId) {\n        this.logger.debug(`[DKG SYNC] Setting up sync mechanism for blockchain ${blockchainId}`);\n\n        // Set up intervals\n        let isMissedRunning = false;\n        const intervalMissed = setInterval(async () => {\n            if (isMissedRunning) {\n                this.logger.debug(\n                    `[DKG SYNC] Sync missed KC mechanism for ${blockchainId} still running, skipping this interval`,\n                );\n                return;\n            }\n\n            try {\n                isMissedRunning = true;\n                this.logger.debug(\n                    `[DKG SYNC] Starting sync missed KC cycle for blockchain ${blockchainId}`,\n                );\n\n                const syncRecords = (\n                    await this.repositoryModuleManager.getSyncRecordForBlockchain(blockchainId)\n                ).map((syncRecord) => syncRecord.toJSON());\n\n                // Run missed KC sync for each contract in parallel\n                await Promise.all(\n                    syncRecords.map((record) =>\n                        this.runSyncMissed(blockchainId, record.contractAddress),\n                    ),\n                );\n                this.logger.debug(\n                    `[DKG SYNC] Completed sync missed KC cycle for blockchain ${blockchainId}`,\n                );\n            } catch (error) {\n                this.logger.error(\n                    `[DKG SYNC] Error in sync missed KC mechanism for ${blockchainId}: ${error.message}, stack: ${error.stack}`,\n                );\n                this.operationIdService.emitChangeEvent(\n                    OPERATION_ID_STATUS.SYNC.SYNC_MISSED_FAILED,\n                    uuidv4(),\n                    blockchainId,\n                    error.message,\n                    error.stack,\n                );\n            } finally {\n                isMissedRunning = false;\n            }\n        }, SYNC_INTERVAL);\n\n        let isNewRunning = false;\n        const intervalNew = setInterval(async () => {\n            if (isNewRunning) {\n                this.logger.debug(\n                    `[DKG SYNC] Sync new KC mechanism for ${blockchainId} still running, skipping this interval`,\n                );\n                return;\n            }\n\n            try {\n                isNewRunning = true;\n                this.logger.debug(\n                    `[DKG SYNC] Starting sync new KC cycle for blockchain ${blockchainId}`,\n                );\n\n                const syncRecords = (\n                    await this.repositoryModuleManager.getSyncRecordForBlockchain(blockchainId)\n                ).map((syncRecord) => syncRecord.toJSON());\n\n                await this.runSyncNewKc(blockchainId, syncRecords);\n                this.logger.debug(\n                    `[DKG SYNC] Completed sync new KC cycle for blockchain ${blockchainId}`,\n                );\n            } catch (error) {\n                this.logger.error(\n                    `[DKG SYNC] Error in sync new KC mechanism for ${blockchainId}: ${error.message}, stack: ${error.stack}`,\n                );\n                this.operationIdService.emitChangeEvent(\n                    OPERATION_ID_STATUS.SYNC.SYNC_NEW_FAILED,\n                    uuidv4(),\n                    blockchainId,\n                    error.message,\n                    error.stack,\n                );\n            } finally {\n                isNewRunning = false;\n            }\n        }, SYNC_INTERVAL);\n\n        // Register intervals for the cleanup\n        this.registeredIntervals.push(intervalMissed);\n        this.registeredIntervals.push(intervalNew);\n\n        // this[`${blockchainId}Interval`] = interval;\n        this.logger.info(`[DKG SYNC] Sync mechanism initialized for blockchain ${blockchainId}`);\n    }\n\n    async runSyncNewKc(blockchainId, syncRecords) {\n        const syncOperationId = uuidv4();\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.SYNC.SYNC_NEW_START,\n            syncOperationId,\n            blockchainId,\n        );\n\n        const latestKnowledgeCollectionIds = {};\n\n        const knowledgeCollectionResults = await Promise.all(\n            syncRecords.map(async (syncRecord) => {\n                const latestKnowledgeCollectionId =\n                    await this.blockchainModuleManager.getLatestKnowledgeCollectionId(\n                        blockchainId,\n                        syncRecord.contractAddress,\n                    );\n\n                return {\n                    contractAddress: syncRecord.contractAddress,\n                    latestKnowledgeCollectionId,\n                    latestSyncedKc: syncRecord.latestSyncedKc,\n                };\n            }),\n        );\n\n        // Filter out null results and build the latestKnowledgeCollectionIds object\n        knowledgeCollectionResults.forEach((result) => {\n            if (result !== null) {\n                latestKnowledgeCollectionIds[result.contractAddress] = {\n                    latestKnowledgeCollectionId: result.latestKnowledgeCollectionId,\n                    latestSyncedKc: result.latestSyncedKc,\n                };\n            }\n        });\n\n        if (this.syncStatus && this.syncStatus[blockchainId]) {\n            const totallatestKnowledgeCollectionId = Object.values(\n                this.syncStatus[blockchainId],\n            ).reduce((acc, curr) => acc + curr.latestKnowledgeCollectionId, 0);\n            const totalLatestSyncedKc = Object.values(this.syncStatus[blockchainId]).reduce(\n                (acc, curr) => acc + curr.latestSyncedKc,\n                0,\n            );\n            const totalMissedKc = Object.values(this.syncStatus[blockchainId]).reduce(\n                (acc, curr) => acc + curr.missedKc,\n                0,\n            );\n            this.operationIdService.emitChangeEvent(\n                OPERATION_ID_STATUS.SYNC.SYNC_PROGRESS_STATUS,\n                syncOperationId,\n                blockchainId,\n                totalLatestSyncedKc,\n                totalMissedKc,\n                totallatestKnowledgeCollectionId,\n            );\n\n            const totalMissedKcChecked =\n                !Number.isFinite(totalMissedKc) || Number.isNaN(totalMissedKc) ? 0 : totalMissedKc;\n            const syncPrecentage =\n                (100 * (totalLatestSyncedKc - totalMissedKcChecked)) /\n                totallatestKnowledgeCollectionId;\n\n            this.logger.info(\n                `[DKG SYNC] DKG Sync for blockchain ${blockchainId} Status: ${syncPrecentage}%`,\n            );\n        }\n\n        const contractPromises = Object.entries(latestKnowledgeCollectionIds).map(\n            async ([contractAddress, syncObject]) => {\n                await this.syncNewKc(blockchainId, contractAddress, syncObject);\n            },\n        );\n\n        await Promise.all(contractPromises);\n\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.SYNC.SYNC_NEW_END,\n            syncOperationId,\n            blockchainId,\n        );\n    }\n\n    async runSyncMissed(blockchainId, contractAddress) {\n        const syncOperationId = uuidv4();\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.SYNC.SYNC_MISSED_START,\n            syncOperationId,\n            blockchainId,\n        );\n\n        await this.syncMissedKc(blockchainId, contractAddress);\n\n        this.operationIdService.emitChangeEvent(\n            OPERATION_ID_STATUS.SYNC.SYNC_MISSED_END,\n            syncOperationId,\n            blockchainId,\n        );\n    }\n\n    // TODO: Add syncOperationId with additional events to syncNewKc\n    async syncNewKc(blockchainId, contractAddress, syncObject) {\n        const uals = [];\n        const { latestSyncedKc } = syncObject;\n        const latestKnowledgeCollectionId = syncObject.latestKnowledgeCollectionId.toNumber();\n        if (!this.syncStatus[blockchainId]) {\n            this.syncStatus[blockchainId] = {};\n        }\n        if (!this.syncStatus[blockchainId][contractAddress]) {\n            this.syncStatus[blockchainId][contractAddress] = {};\n        }\n        this.syncStatus[blockchainId][contractAddress].latestSyncedKc = latestSyncedKc;\n        this.syncStatus[blockchainId][contractAddress].latestKnowledgeCollectionId =\n            latestKnowledgeCollectionId;\n\n        // Calculate upper bound\n        const maxId = Math.min(\n            latestKnowledgeCollectionId,\n            latestSyncedKc + this.syncBatchSize,\n            latestSyncedKc + BATCH_GET_UAL_MAX_LIMIT,\n        );\n\n        // Generate UALs from (latestSyncedKc + 1) to maxId\n        for (let id = latestSyncedKc + 1; id <= maxId; id += 1) {\n            const ual = this.ualService.deriveUAL(blockchainId, contractAddress, id);\n            uals.push(ual);\n        }\n\n        if (uals.length === 0) {\n            this.logger.info(`[DKG SYNC] No UALs to sync for blockchain ${blockchainId}`);\n            return;\n        }\n\n        const { batchGetResult, batchGetOperationId } = await this.callBatchGet(uals, blockchainId);\n\n        if (batchGetResult?.status !== OPERATION_ID_STATUS.COMPLETED) {\n            throw new Error(\n                `[DKG SYNC] Unable to Batch GET Knowledge Collection for blockchain: ${blockchainId}, GET result: ${JSON.stringify(\n                    batchGetResult,\n                )}`,\n            );\n        }\n\n        let insertFailed = false;\n        const data = await this.operationIdService.getCachedOperationIdData(batchGetOperationId);\n\n        if (Object.values(data.remote).length > 0) {\n            // Update metadata timestamps\n            const updatedMetadata = { ...data.metadata };\n            Object.entries(updatedMetadata).forEach(([ual, triples]) => {\n                if (Array.isArray(triples)) {\n                    updatedMetadata[ual] = triples.map((triple) => {\n                        if (triple.includes(DKG_METADATA_PREDICATES.PUBLISH_TIME)) {\n                            const splitTriple = triple.split(' ');\n                            return `${splitTriple[0]} ${\n                                splitTriple[1]\n                            } \"${new Date().toISOString()}\"^^<http://www.w3.org/2001/XMLSchema#dateTime> .`;\n                        }\n                        return triple;\n                    });\n                } else {\n                    updatedMetadata[ual] = [];\n                }\n            });\n            data.metadata = updatedMetadata;\n\n            try {\n                await this.tripleStoreService.insertKnowledgeCollectionBatch(\n                    TRIPLE_STORE_REPOSITORY.DKG,\n                    data,\n                );\n            } catch (error) {\n                this.logger.error(\n                    `[SYNC] Unable to insert Knowledge Collections for blockchain: ${blockchainId}, error: ${error.message}`,\n                );\n                insertFailed = true;\n            }\n        }\n\n        const missingUals = uals.filter((ual) => {\n            const isInLocal = data.local.includes(ual);\n            const hasPublic = data.remote[ual]?.public?.length > 0;\n\n            // Insert failed, so if it's not in local, it's a missing UAL\n            if (insertFailed) {\n                return !isInLocal;\n            }\n            // If it's not in local and has no public data, it's a missing UAL\n            return !isInLocal && !hasPublic;\n        });\n\n        const insertRecords = missingUals.map((ual) => {\n            const { knowledgeCollectionId, contract } = this.ualService.resolveUAL(ual);\n            return {\n                kcId: knowledgeCollectionId,\n                contractAddress: contract,\n            };\n        });\n\n        const transaction = await this.repositoryModuleManager.transaction();\n        try {\n            if (insertRecords.length > 0) {\n                const error = 'KC not found on network';\n                await this.repositoryModuleManager.insertMissedKc(\n                    blockchainId,\n                    insertRecords,\n                    error,\n                    { transaction },\n                );\n            }\n            await this.repositoryModuleManager.updateLatestSyncedKc(\n                blockchainId,\n                contractAddress,\n                latestSyncedKc + uals.length,\n                { transaction },\n            );\n            await transaction.commit();\n        } catch (error) {\n            await transaction.rollback();\n            throw error;\n        }\n    }\n\n    // TODO: Add syncOperationId with additional events to syncMissedKc\n    async syncMissedKc(blockchainId, contract) {\n        const missedKcForRetry = await this.repositoryModuleManager.getMissedKcForRetry(\n            blockchainId,\n            contract,\n            this.syncBatchSize > BATCH_GET_UAL_MAX_LIMIT\n                ? BATCH_GET_UAL_MAX_LIMIT\n                : this.syncBatchSize,\n        );\n\n        const missedKcForRetryCount = await this.repositoryModuleManager.getMissedKcForRetryCount(\n            blockchainId,\n            contract,\n        );\n        if (!this.syncStatus[blockchainId]) {\n            this.syncStatus[blockchainId] = {};\n        }\n        if (!this.syncStatus[blockchainId][contract]) {\n            this.syncStatus[blockchainId][contract] = {};\n        }\n        this.syncStatus[blockchainId][contract].missedKc = missedKcForRetryCount;\n\n        if (missedKcForRetry.length === 0) {\n            this.logger.info(`[SYNC] No missed KC for retry for blockchain ${blockchainId}`);\n            return;\n        }\n        // Contracut uals from object\n        const missedUals = missedKcForRetry.map((missedKc) => {\n            const missedKcJson = missedKc.toJSON();\n            return this.ualService.deriveUAL(\n                blockchainId,\n                missedKcJson.contractAddress,\n                missedKcJson.kcId,\n            );\n        });\n        // Call batch get\n        const { batchGetResult, batchGetOperationId } = await this.callBatchGet(\n            missedUals,\n            blockchainId,\n        );\n\n        if (batchGetResult?.status !== OPERATION_ID_STATUS.COMPLETED) {\n            throw new Error(\n                `[SYNC] Unable to Batch GET Knowledge Collection for blockchain: ${blockchainId}, GET result: ${JSON.stringify(\n                    batchGetResult,\n                )}`,\n            );\n        }\n\n        // Insert\n        let insertFailed = false;\n        const data = await this.operationIdService.getCachedOperationIdData(batchGetOperationId);\n        if (Object.values(data.remote).length > 0) {\n            // Update metadata timestamps\n            const updatedMetadata = { ...data.metadata };\n            Object.entries(updatedMetadata).forEach(([ual, triples]) => {\n                if (Array.isArray(triples)) {\n                    updatedMetadata[ual] = triples.map((triple) => {\n                        if (triple.includes(DKG_METADATA_PREDICATES.PUBLISH_TIME)) {\n                            const splitTriple = triple.split(' ');\n                            return `${splitTriple[0]} ${\n                                splitTriple[1]\n                            } \"${new Date().toISOString()}\"^^<http://www.w3.org/2001/XMLSchema#dateTime> .`;\n                        }\n                        return triple;\n                    });\n                } else {\n                    updatedMetadata[ual] = [];\n                }\n            });\n            data.metadata = updatedMetadata;\n\n            try {\n                await this.tripleStoreService.insertKnowledgeCollectionBatch('dkg', data);\n            } catch (error) {\n                this.logger.error(\n                    `[DKG SYNC] Unable to insert Knowledge Collection for blockchain: ${blockchainId}`,\n                );\n                insertFailed = true;\n            }\n        }\n\n        const missingUals = [];\n        const syncedUals = [];\n\n        missedUals.forEach((ual) => {\n            const isLocal = data.local.includes(ual);\n            const hasRemoteData = data.remote[ual]?.public?.length > 0;\n            // If insert failed, and KC not locally present, add it to missed UALs\n            if (insertFailed) {\n                if (!isLocal) {\n                    missingUals.push(ual);\n                } else {\n                    syncedUals.push(ual);\n                }\n            }\n            // If insert was successful, and KC is locally present or fetched from remote node, add it to synced UALs\n            else if (isLocal || hasRemoteData) {\n                syncedUals.push(ual);\n            } else {\n                missingUals.push(ual);\n            }\n        });\n\n        const recordsToUpdateForRetry = missingUals.map((ual) => {\n            const { knowledgeCollectionId, contract: ualContract } =\n                this.ualService.resolveUAL(ual);\n            return {\n                kcId: knowledgeCollectionId,\n                contractAddress: ualContract,\n            };\n        });\n\n        const recordsToUpdateForSuccess = syncedUals.map((ual) => {\n            const { knowledgeCollectionId, contract: ualContract } =\n                this.ualService.resolveUAL(ual);\n            return {\n                kcId: knowledgeCollectionId,\n                contractAddress: ualContract,\n            };\n        });\n\n        const transaction = await this.repositoryModuleManager.transaction();\n        try {\n            if (recordsToUpdateForRetry.length > 0) {\n                await this.repositoryModuleManager.incrementRetryCount(\n                    blockchainId,\n                    recordsToUpdateForRetry,\n                    { transaction },\n                );\n            }\n            if (recordsToUpdateForSuccess.length > 0) {\n                await this.repositoryModuleManager.setSyncedToTrue(\n                    blockchainId,\n                    recordsToUpdateForSuccess,\n                    { transaction },\n                );\n            }\n            await transaction.commit();\n        } catch (error) {\n            await transaction.rollback();\n            throw error;\n        }\n    }\n\n    async callBatchGet(uals, blockchainId) {\n        const batchGetOperationId = await this.operationIdService.generateOperationId(\n            OPERATION_ID_STATUS.BATCH_GET.BATCH_GET_INIT,\n            blockchainId,\n        );\n\n        await this.commandExecutor.add({\n            name: 'batchGetCommand',\n            sequence: [],\n            delay: 0,\n            data: {\n                operationId: batchGetOperationId,\n                uals,\n                blockchain: blockchainId,\n                includeMetadata: true,\n                contentType: 'all',\n            },\n            transactional: false,\n        });\n\n        let batchGetResult;\n        let attempts = 0;\n        // Poll for result\n        while (attempts < SYNC_BATCH_GET_MAX_ATTEMPTS) {\n            // eslint-disable-next-line no-await-in-loop\n            await setTimeout(SYNC_BATCH_GET_WAIT_TIME);\n            // eslint-disable-next-line no-await-in-loop\n            batchGetResult = await this.operationIdService.getOperationIdRecord(\n                batchGetOperationId,\n            );\n\n            if (\n                batchGetResult?.status === OPERATION_ID_STATUS.FAILED ||\n                batchGetResult?.status === OPERATION_ID_STATUS.COMPLETED\n            ) {\n                break;\n            }\n            attempts += 1;\n        }\n        return { batchGetResult, batchGetOperationId };\n    }\n\n    // Add cleanup method to stop intervals\n    cleanup() {\n        for (const interval of this.registeredIntervals) {\n            clearInterval(interval);\n        }\n    }\n}\n\nexport default SyncService;\n"
  },
  {
    "path": "src/service/triple-store-service.js",
    "content": "/* eslint-disable no-await-in-loop */\nimport { setTimeout } from 'timers/promises';\nimport { kcTools } from 'assertion-tools';\nimport {\n    BASE_NAMED_GRAPHS,\n    TRIPLE_STORE_REPOSITORY,\n    TRIPLES_VISIBILITY,\n    PRIVATE_HASH_SUBJECT_PREFIX,\n    DKG_PREDICATE,\n    HAS_KNOWLEDGE_ASSET_SUFFIX,\n    HAS_NAMED_GRAPH_SUFFIX,\n    DKG_METADATA_PREDICATES,\n    SCHEMA_CONTEXT,\n    MAX_TOKEN_ID_PER_GET_PAGE,\n} from '../constants/constants.js';\n\nclass TripleStoreService {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.config.logging.enableExperimentalScopes\n            ? ctx.logger.child({\n                  scope: 'TripleStoreService',\n              })\n            : ctx.logger;\n\n        this.tripleStoreModuleManager = ctx.tripleStoreModuleManager;\n        this.operationIdService = ctx.operationIdService;\n        this.ualService = ctx.ualService;\n        this.dataService = ctx.dataService;\n        this.paranetService = ctx.paranetService;\n        this.cryptoService = ctx.cryptoService;\n    }\n\n    initializeRepositories() {\n        this.repositoryImplementations = {};\n        for (const implementationName of this.tripleStoreModuleManager.getImplementationNames()) {\n            for (const repository in this.tripleStoreModuleManager.getImplementation(\n                implementationName,\n            ).module.repositories) {\n                this.repositoryImplementations[repository] = implementationName;\n            }\n        }\n    }\n\n    async insertKnowledgeCollection(\n        repository,\n        knowledgeCollectionUAL,\n        triples,\n        metadata,\n        retries = 5,\n        retryDelay = 50,\n        paranetUAL = '',\n        contentType = '',\n    ) {\n        this.logger.info(\n            `Inserting Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n                `to the Triple Store's ${repository} repository.`,\n        );\n\n        const publicAssertion = triples.public ?? triples;\n\n        const filteredPublic = [];\n        const privateHashTriples = [];\n        const tripleSet = new Set();\n\n        let totalNumberOfTriplesInserted = triples?.public\n            ? triples.public.length + (triples.private?.length ?? 0)\n            : triples?.length ?? 0;\n\n        publicAssertion.forEach((triple) => {\n            if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                privateHashTriples.push(triple);\n            } else {\n                filteredPublic.push(triple);\n            }\n        });\n\n        const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n            filteredPublic,\n            true,\n        );\n        publicKnowledgeAssetsTriplesGrouped.push(\n            ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n        );\n\n        const publicKnowledgeAssetsUALs = publicKnowledgeAssetsTriplesGrouped.map(\n            (_, index) => `${knowledgeCollectionUAL}/${index + 1}`,\n        );\n\n        const allPossibleNamedGraphs = [];\n\n        let privateGraphsInsert = '';\n        let currentPrivateMetadataTriples = '';\n        let connectionPrivateMetadataTriples = '';\n\n        const publicGraphsInsert = publicKnowledgeAssetsUALs\n            .map(\n                (ual, index) => `\n            GRAPH <${ual}/${TRIPLES_VISIBILITY.PUBLIC}> {\n                ${publicKnowledgeAssetsTriplesGrouped[index].join('\\n')}\n            }\n        `,\n            )\n            .join('\\n');\n\n        const currentPublicMetadataTriples = publicKnowledgeAssetsUALs\n            .map(\n                (ual) =>\n                    `<current:graph> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${ual}/${TRIPLES_VISIBILITY.PUBLIC}> .`,\n            )\n            .join('\\n');\n\n        const connectionPublicMetadataTriples = publicKnowledgeAssetsUALs\n            .map((ual) => {\n                const graphWithVisibility = `${ual}/${TRIPLES_VISIBILITY.PUBLIC}`;\n                return [\n                    `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`,\n                    `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`,\n                ].join('\\n');\n            })\n            .join('\\n');\n\n        // current metadata triple relates to which named graph that represents Knowledge Asset hold the lates(current) data\n        // so for each Knowledge Asset there will be one current metadata triple\n        // in this case there are publicKnowledgeAssetsUALs.length number of named graphs created so for each there will be one current metadata triple\n        totalNumberOfTriplesInserted += publicKnowledgeAssetsUALs.length;\n\n        publicKnowledgeAssetsUALs.forEach((ual) => {\n            const graphWithVisibility = `${ual}/public`;\n\n            tripleSet.add(\n                `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`,\n            );\n            tripleSet.add(\n                `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`,\n            );\n        });\n\n        this.logger.info(\n            `Adding metadata triples for public asets for Knowledge Collection: ${knowledgeCollectionUAL}`,\n        );\n\n        allPossibleNamedGraphs.push(...publicKnowledgeAssetsUALs.map((ual) => `${ual}/public`));\n\n        if (triples.private?.length) {\n            const privateKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n                triples.private,\n                true,\n            );\n\n            const privateKnowledgeAssetsUALs = [];\n\n            const publicSubjectMap = publicKnowledgeAssetsTriplesGrouped.reduce(\n                (map, group, index) => {\n                    const [publicSubject] = group[0].split(' ');\n                    map.set(publicSubject, index);\n                    return map;\n                },\n                new Map(),\n            );\n\n            for (const privateTriple of privateKnowledgeAssetsTriplesGrouped) {\n                const [privateSubject] = privateTriple[0].split(' ');\n                if (publicSubjectMap.has(privateSubject)) {\n                    const ualIndex = publicSubjectMap.get(privateSubject);\n                    privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]);\n                } else {\n                    const privateSubjectHashed = `<${PRIVATE_HASH_SUBJECT_PREFIX}${this.cryptoService.sha256(\n                        privateSubject.slice(1, -1),\n                    )}>`;\n                    if (publicSubjectMap.has(privateSubjectHashed)) {\n                        const ualIndex = publicSubjectMap.get(privateSubjectHashed);\n                        privateKnowledgeAssetsUALs.push(publicKnowledgeAssetsUALs[ualIndex]);\n                    }\n                }\n            }\n\n            privateGraphsInsert = privateKnowledgeAssetsUALs\n                .map(\n                    (ual, index) => `\n            GRAPH <${ual}/${TRIPLES_VISIBILITY.PRIVATE}> {\n                ${privateKnowledgeAssetsTriplesGrouped[index].join('\\n')}\n            }\n        `,\n                )\n                .join('\\n');\n\n            currentPrivateMetadataTriples = privateKnowledgeAssetsUALs\n                .map(\n                    (ual) =>\n                        `<current:graph> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${ual}/${TRIPLES_VISIBILITY.PRIVATE}> .`,\n                )\n                .join('\\n');\n\n            connectionPrivateMetadataTriples = privateKnowledgeAssetsUALs\n                .map((ual) => {\n                    const graphWithVisibility = `${ual}/${TRIPLES_VISIBILITY.PRIVATE}`;\n                    return [\n                        `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`,\n                        `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`,\n                    ].join('\\n');\n                })\n                .join('\\n');\n\n            // current metadata triple relates to which named graph that represents Knowledge Asset hold the lates(current) data\n            // so for each Knowledge Asset there will be one current metadata triple\n            // in this case there are privateKnowledgeAssetsUALs.length number of named graphs created so for each there will be one current metadata triple\n            totalNumberOfTriplesInserted += privateKnowledgeAssetsUALs.length;\n\n            privateKnowledgeAssetsUALs.forEach((ual) => {\n                const graphWithVisibility = `${ual}/private`;\n\n                tripleSet.add(\n                    `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_KNOWLEDGE_ASSET_SUFFIX}> <${ual}> .`,\n                );\n                tripleSet.add(\n                    `<${knowledgeCollectionUAL}> <${DKG_PREDICATE}${HAS_NAMED_GRAPH_SUFFIX}> <${graphWithVisibility}> .`,\n                );\n            });\n\n            this.logger.info(\n                `Adding metadata triples for private asets for Knowledge Collection: ${knowledgeCollectionUAL}`,\n            );\n\n            allPossibleNamedGraphs.push(\n                ...privateKnowledgeAssetsUALs.map((ual) => `${ual}/private`),\n            );\n        }\n\n        // TODO: add new metadata triples and move to function insertMetadataTriples\n        let metadataTriples = publicKnowledgeAssetsUALs\n            .map(\n                (publicKnowledgeAssetUAL) =>\n                    `<${publicKnowledgeAssetUAL}> <http://schema.org/states> \"${publicKnowledgeAssetUAL}:0\" .`,\n            )\n            .join('\\n');\n\n        metadataTriples +=\n            `\\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.PUBLISHED_BY}> <did:dkg:publisherKey/${metadata.publisherKey}> .` +\n            `\\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.PUBLISHED_AT_BLOCK}> \"${metadata.blockNumber}\" .` +\n            `\\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.PUBLISH_TX}> \"${metadata.txHash}\" .` +\n            `\\n<${knowledgeCollectionUAL}> <${\n                DKG_METADATA_PREDICATES.PUBLISH_TIME\n            }> \"${new Date().toISOString()}\"^^<http://www.w3.org/2001/XMLSchema#dateTime> .` +\n            `\\n<${knowledgeCollectionUAL}> <${DKG_METADATA_PREDICATES.BLOCK_TIME}> \"${new Date(\n                metadata.blockTimestamp * 1000,\n            ).toISOString()}\"^^<http://www.w3.org/2001/XMLSchema#dateTime> .`;\n\n        // totalNumberOfTriplesInserted += publicKnowledgeAssetsUALs.length + 5; // one metadata triple for each public KA\n        const insertQuery = `\n            PREFIX schema: <${SCHEMA_CONTEXT}>\n            INSERT DATA {\n                ${publicGraphsInsert}\n                ${privateGraphsInsert}\n                GRAPH <${BASE_NAMED_GRAPHS.CURRENT}> {\n                    ${currentPublicMetadataTriples}\n                    ${currentPrivateMetadataTriples}\n                }\n                GRAPH <${BASE_NAMED_GRAPHS.METADATA}> {\n                    ${connectionPublicMetadataTriples}\n                    ${connectionPrivateMetadataTriples}\n                    ${metadataTriples}\n                }\n\n            }\n        `;\n\n        const uniqueTripleCount = tripleSet.size;\n        totalNumberOfTriplesInserted += uniqueTripleCount;\n\n        let attempts = 0;\n        let success = false;\n\n        while (attempts < retries && !success) {\n            try {\n                await this.tripleStoreModuleManager.queryVoid(\n                    this.repositoryImplementations[repository],\n                    repository,\n                    insertQuery,\n                    this.config.modules.tripleStore.timeout.insert,\n                );\n                if (paranetUAL) {\n                    await this.tripleStoreModuleManager.createParanetKnoledgeCollectionConnection(\n                        this.repositoryImplementations[repository],\n                        repository,\n                        knowledgeCollectionUAL,\n                        paranetUAL,\n                        contentType,\n                        this.config.modules.tripleStore.timeout.insert,\n                    );\n                    totalNumberOfTriplesInserted += allPossibleNamedGraphs.length; // one triple will be created for each Knowledge Asset inserted into paranet\n                    this.logger.info(`Adding connection triples for paranet: ${paranetUAL}`);\n                }\n                success = true;\n\n                this.logger.info(\n                    `Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n                        `has been successfully inserted to the Triple Store's ${repository} repository.`,\n                );\n            } catch (error) {\n                this.logger.error(\n                    `Error during insertion of the Knowledge Collection to the Triple Store's ${repository} repository. ` +\n                        `UAL: ${knowledgeCollectionUAL}. Error: ${error.message}`,\n                );\n                attempts += 1;\n\n                if (attempts < retries) {\n                    this.logger.info(\n                        `Retrying insertion of the Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n                            `to the Triple Store's ${repository} repository. Attempt ${\n                                attempts + 1\n                            } of ${retries} after delay of ${retryDelay} ms.`,\n                    );\n                    await setTimeout(retryDelay);\n                } else {\n                    this.logger.error(\n                        `Max retries reached for the insertion of the Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n                            `to the Triple Store's ${repository} repository. Rolling back data.`,\n                    );\n\n                    this.logger.info(\n                        `Rolling back Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n                            `from the Triple Store's ${repository} repository Named Graphs.`,\n                    );\n\n                    await Promise.all([\n                        this.tripleStoreModuleManager.deleteKnowledgeCollectionNamedGraphs(\n                            this.repositoryImplementations[repository],\n                            repository,\n                            allPossibleNamedGraphs,\n                        ),\n                        this.tripleStoreModuleManager.deleteKnowledgeCollectionMetadata(\n                            this.repositoryImplementations[repository],\n                            repository,\n                            allPossibleNamedGraphs,\n                        ),\n                    ]);\n\n                    throw new Error(\n                        `Failed to store Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n                            `to the Triple Store's ${repository} repository after maximum retries. Error ${error}`,\n                    );\n                }\n            }\n        }\n\n        return totalNumberOfTriplesInserted;\n    }\n\n    async insertKnowledgeCollectionBatch(repository, KCs) {\n        // this.logger.info(\n        //     `Inserting Knowledge Collection with the UAL: ${knowledgeCollectionUAL} ` +\n        //         `to the Triple Store's ${repository} repository.`,\n        // );\n        // This metadata is not validated\n        const { remote, metadata } = KCs;\n        const insert = {};\n        const createdMetadata = [];\n        const currentNamedGraphTriples = [];\n        // remote { ual: { public: [triples], private: [triples] } }\n        for (const ual of Object.keys(remote)) {\n            const triples = remote[ual].public;\n            const filteredPublic = [];\n            const privateHashTriples = [];\n\n            triples.forEach((triple) => {\n                if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                    privateHashTriples.push(triple);\n                } else {\n                    filteredPublic.push(triple);\n                }\n            });\n\n            const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n                filteredPublic,\n                true,\n            );\n            publicKnowledgeAssetsTriplesGrouped.push(\n                ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n            );\n\n            const publicKnowledgeAssetsUALs = publicKnowledgeAssetsTriplesGrouped.map(\n                (_, index) => `${ual}/${index + 1}`,\n            );\n\n            for (const [index, kaUAL] of publicKnowledgeAssetsUALs.entries()) {\n                insert[`${kaUAL}/public`] = publicKnowledgeAssetsTriplesGrouped[index];\n                createdMetadata.push(`<${kaUAL}> <http://schema.org/states> \"${kaUAL}:0\" .`);\n                currentNamedGraphTriples.push(\n                    `<current:graph> <https://ontology.origintrail.io/dkg/1.0#hasNamedGraph> <${kaUAL}/public> .`,\n                );\n                createdMetadata.push(\n                    `<${ual}> <https://ontology.origintrail.io/dkg/1.0#hasKnowledgeAsset> <${kaUAL}> .`,\n                );\n            }\n        }\n\n        await this.tripleStoreModuleManager.insertAssertionBatch(\n            TRIPLE_STORE_REPOSITORY.DKG,\n            repository,\n            insert,\n            metadata,\n            createdMetadata,\n            currentNamedGraphTriples,\n            this.config.modules.tripleStore.timeout.insert,\n        );\n    }\n\n    async deletePublishTimestampMetadata(repository, ual) {\n        await this.tripleStoreModuleManager.deletePublishTimestampMetadata(\n            this.repositoryImplementations[repository],\n            repository,\n            ual,\n        );\n    }\n\n    async checkIfKnowledgeCollectionExistsInUnifiedGraph(\n        ual,\n        repository = TRIPLE_STORE_REPOSITORY.DKG,\n    ) {\n        const knowledgeCollectionExists =\n            await this.tripleStoreModuleManager.knowledgeCollectionExistsInUnifiedGraph(\n                this.repositoryImplementations[repository],\n                repository,\n                BASE_NAMED_GRAPHS.UNIFIED,\n                ual,\n            );\n\n        return knowledgeCollectionExists;\n    }\n\n    async getAssertion(\n        blockchain,\n        contract,\n        knowledgeCollectionId,\n        knowledgeAssetId,\n        tokenIds,\n        migrationFlag,\n        visibility = TRIPLES_VISIBILITY.PUBLIC,\n        repository = TRIPLE_STORE_REPOSITORY.DKG,\n        operationId = undefined,\n    ) {\n        // TODO: Use stateId\n        let ual = `did:dkg:${blockchain}/${contract}/${knowledgeCollectionId}`;\n\n        // Performance instrumentation (enable only if operationId is supplied)\n        const logTime = operationId !== undefined;\n        const startTimer = (label) => {\n            if (logTime) this.logger.startTimer(label);\n        };\n        const endTimer = (label) => {\n            if (logTime) this.logger.endTimer(label);\n        };\n\n        const totalLabel = `[TripleStoreService.getAssertion TOTAL] ${operationId} ${ual}`;\n        startTimer(totalLabel);\n\n        let nquads = {};\n        if (typeof knowledgeAssetId === 'number') {\n            ual = `${ual}/${knowledgeAssetId}`;\n            const singleLabel = `[TripleStoreService.getAssertion SINGLE] ${operationId} ${ual}`;\n            startTimer(singleLabel);\n\n            this.logger.debug(`Getting Assertion with the UAL: ${ual}.`);\n            nquads = await this.tripleStoreModuleManager.getKnowledgeAssetNamedGraph(\n                this.repositoryImplementations[repository],\n                repository,\n                // TODO: Add state with implemented update\n                `${ual}`,\n                visibility,\n                this.config.modules.tripleStore.timeout.get,\n            );\n\n            endTimer(singleLabel);\n        } else {\n            this.logger.debug(`Getting Assertion with the UAL: ${ual}.`);\n\n            const existsLabel = `[TripleStoreService.getAssertion EXISTS_CHECK] ${operationId} ${ual}`;\n            startTimer(existsLabel);\n\n            // first check if the knowledge collection exists in triple store using ASK\n            const firstKAInCollection = `${ual}/${tokenIds.startTokenId}/${TRIPLES_VISIBILITY.PUBLIC}`;\n            const lastKAInCollection = `${ual}/${tokenIds.endTokenId}/${TRIPLES_VISIBILITY.PUBLIC}`;\n            const firstKAExists = this.tripleStoreModuleManager.checkIfKnowledgeAssetExists(\n                this.repositoryImplementations[repository],\n                repository,\n                firstKAInCollection,\n                this.config.modules.tripleStore.timeout.ask,\n            );\n            const lastKAExists = this.tripleStoreModuleManager.checkIfKnowledgeAssetExists(\n                this.repositoryImplementations[repository],\n                repository,\n                lastKAInCollection,\n                this.config.modules.tripleStore.timeout.ask,\n            );\n\n            const [firstKAResult, lastKAResult] = await Promise.all([firstKAExists, lastKAExists]);\n\n            endTimer(existsLabel);\n\n            if (!(firstKAResult && lastKAResult)) {\n                this.logger.warn(\n                    `Knowledge Collection with the UAL: ${ual} does not exist in the Triple Store's ${repository} repository.`,\n                );\n                endTimer(totalLabel);\n                return { public: [], private: [] };\n            }\n            // tokenIds are used to construct named graphs\n            // do pagination through tokenIds\n\n            const collectionLabel = `[TripleStoreService.getAssertion COLLECTION] ${operationId} ${ual}`;\n            startTimer(collectionLabel);\n\n            if (visibility === TRIPLES_VISIBILITY.PUBLIC || visibility === TRIPLES_VISIBILITY.ALL) {\n                nquads.public = [];\n            }\n            if (\n                visibility === TRIPLES_VISIBILITY.PRIVATE ||\n                visibility === TRIPLES_VISIBILITY.ALL\n            ) {\n                nquads.private = [];\n            }\n            const maxTokenId = tokenIds.endTokenId;\n            for (let i = 0; i <= tokenIds.endTokenId; i += MAX_TOKEN_ID_PER_GET_PAGE) {\n                const paginationNquads =\n                    await this.tripleStoreModuleManager.getKnowledgeCollectionNamedGraphsOld(\n                        this.repositoryImplementations[repository],\n                        repository,\n                        ual,\n                        {\n                            startTokenId: i + 1,\n                            endTokenId: Math.min(i + MAX_TOKEN_ID_PER_GET_PAGE, maxTokenId),\n                            burned: tokenIds.burned,\n                        },\n                        visibility,\n                        this.config.modules.tripleStore.timeout.get,\n                    );\n                if (paginationNquads?.public) {\n                    nquads.public.push(\n                        ...paginationNquads.public.split('\\n').filter((line) => line !== ''),\n                    );\n                }\n                if (paginationNquads?.private) {\n                    nquads.private.push(\n                        ...paginationNquads.private.split('\\n').filter((line) => line !== ''),\n                    );\n                }\n            }\n\n            endTimer(collectionLabel);\n        }\n\n        const numberOfnquads = (nquads?.public?.length ?? 0) + (nquads?.private?.length ?? 0);\n\n        this.logger.debug(\n            `Assertion: ${ual} ${\n                numberOfnquads ? '' : 'is not'\n            } found in the Triple Store's ${repository} repository.`,\n        );\n\n        if (nquads.length) {\n            this.logger.debug(\n                `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${numberOfnquads}.`,\n            );\n        }\n\n        endTimer(totalLabel);\n        return nquads;\n    }\n\n    async getAssertionsInBatch(\n        repository,\n        uals,\n        ualTokenIds,\n        visibility = 'public',\n        operationId = undefined,\n    ) {\n        // Conditional performance logging\n        const logTime = operationId !== undefined;\n        const startTimer = (label) => {\n            if (logTime) this.logger.startTimer(label);\n        };\n        const endTimer = (label) => {\n            if (logTime) this.logger.endTimer(label);\n        };\n\n        const totalLabel = `[TripleStoreService.getAssertionsInBatch TOTAL] ${operationId} ${uals.length}`;\n        startTimer(totalLabel);\n\n        const results = await Promise.all(\n            uals.map(async (ual) => {\n                const { blockchain, contract, knowledgeCollectionId } =\n                    this.ualService.resolveUAL(ual);\n                return this.getAssertion(\n                    blockchain,\n                    contract,\n                    knowledgeCollectionId,\n                    null,\n                    ualTokenIds[ual],\n                    false,\n                    visibility,\n                    repository,\n                    operationId,\n                );\n            }),\n        );\n\n        const result = {};\n        for (const [index, ual] of uals.entries()) {\n            result[ual] = results[index];\n        }\n\n        endTimer(totalLabel);\n        return result;\n    }\n\n    async getV6Assertion(repository, assertionId) {\n        this.logger.debug(\n            `Getting Assertion with the ID: ${assertionId} from the Triple Store's ${repository} repository.`,\n        );\n        const nquads = await this.tripleStoreModuleManager.getV6Assertion(\n            this.repositoryImplementations[repository],\n            repository,\n            assertionId,\n        );\n\n        this.logger.debug(\n            `Assertion: ${assertionId} ${\n                nquads.length ? '' : 'is not'\n            } found in the Triple Store's ${repository} repository.`,\n        );\n\n        if (nquads.length) {\n            this.logger.debug(\n                `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${nquads.length}.`,\n            );\n        }\n\n        return nquads;\n    }\n\n    async checkIfKnowledgeAssetExists(repository, kaUAL) {\n        const knowledgeAssetExists =\n            await this.tripleStoreModuleManager.checkIfKnowledgeAssetExists(\n                this.repositoryImplementations[repository],\n                repository,\n                kaUAL,\n            );\n        return knowledgeAssetExists;\n    }\n\n    async getAssertionMetadata(\n        blockchain,\n        contract,\n        knowledgeCollectionId,\n        repository = TRIPLE_STORE_REPOSITORY.DKG,\n    ) {\n        const ual = `did:dkg:${blockchain}/${contract}/${knowledgeCollectionId}`;\n        this.logger.debug(`Getting Assertion Metadata with the UAL: ${ual}.`);\n\n        let nquads = await this.tripleStoreModuleManager.getKnowledgeCollectionMetadata(\n            this.repositoryImplementations[repository],\n            repository,\n            ual,\n            this.config.modules.tripleStore.timeout.get,\n        );\n\n        nquads = nquads.split('\\n').filter((line) => line !== '');\n\n        this.logger.debug(\n            `Knowledge Asset Metadata: ${ual} ${\n                nquads.length ? '' : 'is not'\n            } found in the Triple Store's ${repository} repository.`,\n        );\n\n        if (nquads.length) {\n            this.logger.debug(\n                `Number of n-quads retrieved from the Triple Store's ${repository} repository: ${nquads.length}.`,\n            );\n        }\n\n        return nquads;\n    }\n\n    async getAssertionMetadataBatch(uals) {\n        const metadataTriples = await this.tripleStoreModuleManager.getMetadataInBatch(\n            this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG],\n            TRIPLE_STORE_REPOSITORY.DKG,\n            uals,\n        );\n\n        const metadata = {};\n        for (const line of metadataTriples.split('\\n').filter((result) => result !== '')) {\n            const splitLine = line.split(' ');\n            const ual = splitLine[0].replace(/[<>]/g, '');\n            if (!metadata[ual]) {\n                metadata[ual] = [line];\n            } else {\n                metadata[ual].push(line);\n            }\n        }\n\n        return metadata;\n    }\n\n    async getLatestAssertionId(repository, ual) {\n        const nquads = await this.tripleStoreModuleManager.getLatestAssertionId(\n            this.repositoryImplementations[repository],\n            repository,\n            ual,\n        );\n\n        return nquads;\n    }\n\n    async construct(query, repository = TRIPLE_STORE_REPOSITORY.DKG, timeout = 60000) {\n        return this.tripleStoreModuleManager.construct(\n            this.repositoryImplementations[repository] ??\n                this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG],\n            repository,\n            query,\n            timeout,\n        );\n    }\n\n    async getKnowledgeAssetNamedGraph(repository, ual, visibility, timeout) {\n        return this.tripleStoreModuleManager.getKnowledgeAssetNamedGraph(\n            this.repositoryImplementations[repository],\n            repository,\n            ual,\n            visibility,\n            timeout,\n        );\n    }\n\n    async select(query, repository = TRIPLE_STORE_REPOSITORY.DKG, timeout = 60000) {\n        return this.tripleStoreModuleManager.select(\n            this.repositoryImplementations[repository] ??\n                this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG],\n            repository,\n            query,\n            timeout,\n        );\n    }\n\n    async ask(query, repository = TRIPLE_STORE_REPOSITORY.DKG) {\n        return this.tripleStoreModuleManager.ask(\n            this.repositoryImplementations[repository] ??\n                this.repositoryImplementations[TRIPLE_STORE_REPOSITORY.DKG],\n            repository,\n            query,\n        );\n    }\n\n    getRepositorySparqlEndpoint(repository) {\n        const implementationName = this.repositoryImplementations[repository];\n        const endpoint =\n            this.tripleStoreModuleManager.getImplementation(implementationName).module.repositories[\n                repository\n            ].sparqlEndpoint;\n        return endpoint;\n    }\n}\n\nexport default TripleStoreService;\n"
  },
  {
    "path": "src/service/ual-service.js",
    "content": "class UALService {\n    constructor(ctx) {\n        this.config = ctx.config;\n        this.logger = ctx.logger;\n\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n        this.cryptoService = ctx.cryptoService;\n    }\n\n    deriveUAL(blockchain, contract, knowledgeCollectionId, knowledgeAssetId) {\n        const ual = `did:dkg:${blockchain.toLowerCase()}/${contract.toLowerCase()}/${knowledgeCollectionId}`;\n        return knowledgeAssetId ? `${ual}/${knowledgeAssetId}` : ual;\n    }\n\n    // did:dkg:otp:2043/0x123231/5\n    isUAL(ual) {\n        if (!ual.startsWith('did:dkg:')) return false;\n        const parts = ual.replace('did:', '').replace('dkg:', '').split('/');\n        parts.push(...parts.pop().split(':'));\n        if (parts.length === 4) {\n            return (\n                this.isContract(parts[1]) &&\n                !Number.isNaN(Number(parts[2])) &&\n                !Number.isNaN(Number(parts[3]))\n            );\n        }\n        if (parts.length === 3) {\n            // eslint-disable-next-line no-restricted-globals\n            return this.isContract(parts[1]) && !Number.isNaN(Number(parts[2]));\n        }\n        if (parts.length === 2) {\n            const parts2 = parts[0].split(':');\n            // eslint-disable-next-line no-restricted-globals\n            if (parts2.length === 3) {\n                return (\n                    parts2.length === 2 &&\n                    this.isContract(parts2[2]) &&\n                    !Number.isNaN(Number(parts[1]))\n                );\n            }\n            return (\n                parts2.length === 2 && this.isContract(parts2[1]) && !Number.isNaN(Number(parts[1]))\n            );\n        }\n    }\n\n    resolveUAL(ual) {\n        const parts = ual.replace('did:', '').replace('dkg:', '').split('/');\n        // TODO: Resolve UAL with state\n        // parts.push(...parts.pop().split(':'));\n        if (parts.length === 4) {\n            const contract = parts[1];\n            if (!this.isContract(contract)) {\n                throw new Error(`Invalid contract format: ${contract}`);\n            }\n            let blockchainName = parts[0];\n            if (blockchainName.split(':').length === 1) {\n                for (const implementation of this.blockchainModuleManager.getImplementationNames()) {\n                    if (implementation.split(':')[0] === blockchainName) {\n                        blockchainName = implementation;\n                        break;\n                    }\n                }\n            }\n            return {\n                blockchain: blockchainName,\n                contract,\n                knowledgeCollectionId: Number(parts[2]),\n                knowledgeAssetId: Number(parts[3]),\n            };\n        }\n        if (parts.length === 3) {\n            const contract = parts[1];\n            if (!this.isContract(contract)) {\n                throw new Error(`Invalid contract format: ${contract}`);\n            }\n            let blockchainName = parts[0];\n            if (blockchainName.split(':').length === 1) {\n                for (const implementation of this.blockchainModuleManager.getImplementationNames()) {\n                    if (implementation.split(':')[0] === blockchainName) {\n                        blockchainName = implementation;\n                        break;\n                    }\n                }\n            }\n            return {\n                blockchain: blockchainName,\n                contract,\n                knowledgeCollectionId: Number(parts[2]),\n            };\n        }\n        if (parts.length === 2) {\n            const parts2 = parts[0].split(':');\n            if (parts2.length === 3) {\n                const contract = parts2[2];\n                if (!this.isContract(contract)) {\n                    throw new Error(`Invalid contract format: ${contract}`);\n                }\n                return {\n                    blockchain: parts2[0] + parts2[1],\n                    contract,\n                    knowledgeCollectionId: Number(parts[1]),\n                };\n            }\n            if (parts2.length === 2) {\n                let blockchainWithId;\n                for (const implementation of this.blockchainModuleManager.getImplementationNames()) {\n                    if (implementation.split(':')[0] === blockchainWithId) {\n                        blockchainWithId = implementation;\n                        break;\n                    }\n                }\n                const contract = parts2[1];\n                if (!this.isContract(contract)) {\n                    throw new Error(`Invalid contract format: ${contract}`);\n                }\n                return {\n                    blockchain: blockchainWithId,\n                    contract,\n                    knowledgeCollectionId: Number(parts[1]),\n                };\n            }\n        }\n\n        throw new Error(`UAL doesn't have correct format: ${ual}`);\n    }\n\n    isContract(contract) {\n        const contractRegex = /^0x[a-fA-F0-9]{40}$/;\n        return contractRegex.test(contract);\n    }\n\n    // TODO: Do we need still need this\n    async calculateLocationKeyword(\n        blockchain,\n        contract,\n        knowledgeCollectionId,\n        assertionId = null,\n    ) {\n        const firstAssertionId =\n            assertionId ??\n            (await this.blockchainModuleManager.getKnowledgeCollectionMerkleRootByIndex(\n                blockchain,\n                contract,\n                knowledgeCollectionId,\n                0,\n            ));\n        return this.cryptoService.encodePacked(\n            ['address', 'bytes32'],\n            [contract, firstAssertionId],\n        );\n    }\n\n    getUalWithoutChainId(ual, blockchain) {\n        const blockchainParts = blockchain.split(':');\n\n        if (ual.includes(blockchain)) {\n            return ual.replace(blockchain, blockchainParts[0]);\n        }\n        return ual;\n    }\n}\n\nexport default UALService;\n"
  },
  {
    "path": "src/service/update-service.js",
    "content": "import OperationService from './operation-service.js';\n\nimport {\n    OPERATION_ID_STATUS,\n    NETWORK_PROTOCOLS,\n    ERROR_TYPE,\n    OPERATIONS,\n} from '../constants/constants.js';\n\nclass UpdateService extends OperationService {\n    constructor(ctx) {\n        super(ctx);\n\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n\n        this.operationName = OPERATIONS.UPDATE;\n        this.networkProtocols = NETWORK_PROTOCOLS.UPDATE;\n        this.errorType = ERROR_TYPE.UPDATE.UPDATE_ERROR;\n        this.completedStatuses = [\n            OPERATION_ID_STATUS.UPDATE.UPDATE_REPLICATE_END,\n            OPERATION_ID_STATUS.UPDATE.UPDATE_END,\n            OPERATION_ID_STATUS.COMPLETED,\n        ];\n    }\n\n    async processResponse(command, responseStatus, responseData, errorMessage = null) {\n        const {\n            operationId,\n            blockchain,\n            numberOfFoundNodes,\n            leftoverNodes,\n            batchSize,\n            minAckResponses,\n            datasetRoot,\n        } = command.data;\n\n        const datasetRootStatus = await this.getResponsesStatuses(\n            responseStatus,\n            errorMessage,\n            operationId,\n        );\n\n        const { completedNumber, failedNumber } = datasetRootStatus[operationId];\n\n        const totalResponses = completedNumber + failedNumber;\n\n        this.logger.debug(\n            `Processing ${\n                this.operationName\n            } response with status: ${responseStatus} for operationId: ${operationId}. Total number of nodes: ${numberOfFoundNodes}, number of nodes in batch: ${Math.min(\n                numberOfFoundNodes,\n                batchSize,\n            )} number of leftover nodes: ${\n                leftoverNodes.length\n            }, number of responses: ${totalResponses}, Completed: ${completedNumber}, Failed: ${failedNumber}, minimum replication factor: ${minAckResponses}`,\n        );\n        if (responseData.errorMessage) {\n            this.logger.trace(\n                `Error message for operation id: ${operationId} : ${responseData.errorMessage}`,\n            );\n        }\n\n        // Minimum replication reached, mark in the operational DB\n        if (completedNumber === minAckResponses) {\n            this.logger.debug(\n                `Minimum replication ${minAckResponses} reached for operationId: ${operationId}, dataset root: ${datasetRoot}`,\n            );\n\n            await this.repositoryModuleManager.updateMinAcksReached(operationId, true);\n        }\n\n        // All requests sent, minimum replication reached, mark as completed\n        if (leftoverNodes.length === 0 && completedNumber >= minAckResponses) {\n            await this.markOperationAsCompleted(\n                operationId,\n                blockchain,\n                null,\n                this.completedStatuses,\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        }\n\n        // All requests sent, minimum replication not reached, mark as failed\n        if (leftoverNodes.length === 0 && completedNumber < minAckResponses) {\n            this.markOperationAsFailed(\n                operationId,\n                blockchain,\n                'Not replicated to enough nodes!',\n                this.errorType,\n            );\n            this.logResponsesSummary(completedNumber, failedNumber);\n        }\n\n        // Not all requests sent, still possible to reach minimum replication,\n        // schedule requests for leftover nodes\n        const potentialCompletedNumber = completedNumber + leftoverNodes.length;\n        if (leftoverNodes.length > 0 && potentialCompletedNumber >= minAckResponses) {\n            await this.scheduleOperationForLeftoverNodes(command.data, leftoverNodes);\n        }\n    }\n}\n\nexport default UpdateService;\n"
  },
  {
    "path": "src/service/util/jwt-util.js",
    "content": "import 'dotenv/config';\nimport jwt from 'jsonwebtoken';\nimport { validate } from 'uuid';\n\nclass JwtUtil {\n    constructor() {\n        this._secret = process.env.JWT_SECRET;\n    }\n\n    /**\n     * Generates new JWT token\n     * @param uuid uuid from token table\n     * @param expiresIn optional parameter. accepts values for ms package (https://www.npmjs.com/package/ms)\n     * @returns {string|null}\n     */\n    generateJWT(uuid, expiresIn = null) {\n        if (!validate(uuid)) {\n            return null;\n        }\n\n        const options = {\n            jwtid: uuid,\n        };\n\n        if (expiresIn) {\n            options.expiresIn = expiresIn;\n        }\n\n        return jwt.sign({}, this._secret, options);\n    }\n\n    /**\n     * Validates JWT token\n     * @param {string} token\n     * @returns {boolean}\n     */\n    validateJWT(token) {\n        try {\n            jwt.verify(token, this._secret);\n        } catch (e) {\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Returns JWT payload\n     * @param {string} token\n     * @returns {*}\n     */\n    getPayload(token) {\n        return jwt.decode(token);\n    }\n\n    /**\n     * Decodes token\n     * @param token\n     * @returns {{payload: any, signature: *, header: *}|*}\n     */\n    decode(token) {\n        return jwt.decode(token, { complete: true });\n    }\n}\n\nconst jwtUtil = new JwtUtil();\n\nexport default jwtUtil;\n"
  },
  {
    "path": "src/service/util/string-util.js",
    "content": "class StringUtil {\n    toCamelCase(str) {\n        return str.replace(/[-_]+(.)/g, (_, group) => group.toUpperCase());\n    }\n\n    capitalize(str) {\n        return str.charAt(0).toUpperCase() + str.slice(1);\n    }\n}\n\nconst stringUtil = new StringUtil();\n\nexport default stringUtil;\n"
  },
  {
    "path": "src/service/validation-service.js",
    "content": "import { kcTools } from 'assertion-tools';\nimport {\n    ZERO_ADDRESS,\n    PRIVATE_ASSERTION_PREDICATE,\n    ZERO_BYTES32,\n    PRIVATE_HASH_SUBJECT_PREFIX,\n} from '../constants/constants.js';\n\nclass ValidationService {\n    constructor(ctx) {\n        this.logger = ctx.logger;\n        this.config = ctx.config;\n        this.validationModuleManager = ctx.validationModuleManager;\n        this.blockchainModuleManager = ctx.blockchainModuleManager;\n    }\n\n    async validateUal(blockchain, contract, tokenId) {\n        this.logger.info(\n            `Validating UAL: did:dkg:${blockchain.toLowerCase()}/${contract.toLowerCase()}/${tokenId}`,\n        );\n\n        let isValid = true;\n        try {\n            const result = await this.blockchainModuleManager.getLatestMerkleRootPublisher(\n                blockchain,\n                contract,\n                tokenId,\n            );\n            if (!result || result === ZERO_ADDRESS) {\n                isValid = false;\n            }\n        } catch (err) {\n            isValid = false;\n        }\n\n        return isValid;\n    }\n\n    async validateUalV6(blockchain, contract, tokenId) {\n        this.logger.info(\n            `Validating UAL: did:dkg:${blockchain.toLowerCase()}/${contract.toLowerCase()}/${tokenId}`,\n        );\n\n        let isValid = true;\n        try {\n            const result = await this.blockchainModuleManager.getLatestAssertionId(\n                blockchain,\n                contract,\n                tokenId,\n            );\n            if (!result || result === ZERO_BYTES32) {\n                isValid = false;\n            }\n        } catch (err) {\n            isValid = false;\n        }\n\n        return isValid;\n    }\n\n    async validateAssertion(assertionId, blockchain, assertion) {\n        this.logger.info(`Validating assertionId: ${assertionId}`);\n\n        await this.validateDatasetRoot(assertion, assertionId);\n\n        this.logger.info(`Assertion integrity validated! AssertionId: ${assertionId}`);\n    }\n\n    async validateDatasetRootOnBlockchain(\n        knowledgeCollectionMerkleRoot,\n        blockchain,\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n    ) {\n        const blockchainAssertionRoot =\n            await this.blockchainModuleManager.getKnowledgeCollectionLatestMerkleRoot(\n                blockchain,\n                assetStorageContractAddress,\n                knowledgeCollectionId,\n            );\n\n        if (knowledgeCollectionMerkleRoot !== blockchainAssertionRoot) {\n            throw new Error(\n                `Merkle Root validation failed. Merkle Root on chain: ${blockchainAssertionRoot}; Calculated Merkle Root: ${knowledgeCollectionMerkleRoot}`,\n            );\n        }\n    }\n\n    // Used to validate assertion node received through network get\n    async validateDatasetOnBlockchain(\n        assertion,\n        blockchain,\n        assetStorageContractAddress,\n        knowledgeCollectionId,\n    ) {\n        const knowledgeCollectionMerkleRoot = await this.validationModuleManager.calculateRoot(\n            assertion,\n        );\n\n        await this.validateDatasetRootOnBlockchain(\n            knowledgeCollectionMerkleRoot,\n            blockchain,\n            assetStorageContractAddress,\n            knowledgeCollectionId,\n        );\n    }\n\n    async validateDatasetRoot(dataset, datasetRoot) {\n        const calculatedDatasetRoot = await this.validationModuleManager.calculateRoot(dataset);\n\n        if (datasetRoot !== calculatedDatasetRoot) {\n            throw new Error(\n                `Merkle Root validation failed. Received Merkle Root: ${datasetRoot}; Calculated Merkle Root: ${calculatedDatasetRoot}`,\n            );\n        }\n    }\n\n    async validatePrivateMerkleRoot(publicAssertion, privateAssertion) {\n        const privateAssertionTriple = publicAssertion.find((triple) =>\n            triple.includes(PRIVATE_ASSERTION_PREDICATE),\n        );\n\n        if (privateAssertionTriple) {\n            const privateAssertionRoot = privateAssertionTriple.split(' ')[2].replace(/['\"]/g, '');\n            // Is this cause of the problem, maybe do it in same was as on client\n            const privateAssertionSorted = privateAssertion.sort();\n            await this.validateDatasetRoot(privateAssertionSorted, privateAssertionRoot);\n        }\n    }\n\n    async validateGetResponse(\n        assertion,\n        blockchain,\n        contract,\n        knowledgeCollectionId,\n        knowledgeAssetId,\n    ) {\n        if (assertion.public) {\n            // We can only validate whole collection not particular KA\n            if (knowledgeAssetId) {\n                const publicAssertion = assertion?.public;\n\n                const filteredPublic = [];\n                const privateHashTriples = [];\n                publicAssertion.forEach((triple) => {\n                    if (triple.startsWith(`<${PRIVATE_HASH_SUBJECT_PREFIX}`)) {\n                        privateHashTriples.push(triple);\n                    } else {\n                        filteredPublic.push(triple);\n                    }\n                });\n\n                const publicKnowledgeAssetsTriplesGrouped = kcTools.groupNquadsBySubject(\n                    filteredPublic,\n                    true,\n                );\n                publicKnowledgeAssetsTriplesGrouped.push(\n                    ...kcTools.groupNquadsBySubject(privateHashTriples, true),\n                );\n\n                try {\n                    await this.validateDatasetOnBlockchain(\n                        publicKnowledgeAssetsTriplesGrouped.map((t) => t.sort()).flat(),\n                        blockchain,\n                        contract,\n                        knowledgeCollectionId,\n                    );\n\n                    if (assertion?.private?.length)\n                        await this.validatePrivateMerkleRoot(assertion.public, assertion.private);\n                } catch (e) {\n                    return false;\n                }\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n}\n\nexport default ValidationService;\n"
  },
  {
    "path": "test/assertions/assertions.js",
    "content": "const context = { xsd: 'http://www.w3.org/2001/XMLSchema#', testProperty: 'http://example.com' };\n\nfunction createTestGraph(id, type, values) {\n    return {\n        '@context': context,\n        '@graph': values.map((value, i) => ({\n            '@id': id + i,\n            testProperty: { '@type': type, '@value': value },\n        })),\n    };\n}\n\n// XSD:DECIMAL\nlet id = 'test:decimal';\nlet type = 'xsd:decimal';\nlet values = [100, 100.0, '100', '100.0'];\nconst decimal = createTestGraph(id, type, values);\n\n// XSD:DATETIME\nid = 'test:dateTime';\ntype = 'xsd:dateTime';\nvalues = [\n    \"2022-08-20'T'13:20:10*633+0000\",\n    '2022 Mar 03 05:12:41.211 PDT',\n    'Jan 21 18:20:11 +0000 2022',\n    '19/Apr/2022:06:36:15 -0700',\n    'Dec 2, 2022 2:39:58 AM',\n    'Jun 09 2022 15:28:14',\n    'Apr 20 00:00:35 2010',\n    'Sep 28 19:00:00 +0000',\n    'Mar 16 08:12:04',\n    '2022-10-14T22:11:20+0000',\n    \"2022-07-01T14:59:55.711'+0000'\",\n    '2022-07-01T14:59:55.711Z',\n    '2022-08-19 12:17:55 -0400',\n    '2022-08-19 12:17:55-0400',\n    '2022-06-26 02:31:29,573',\n    '2022/04/12*19:37:50',\n    '2022 Apr 13 22:08:13.211*PDT',\n    '2022 Mar 10 01:44:20.392',\n    '2022-03-10 14:30:12,655+0000',\n    '2022-02-27 15:35:20.311',\n    '2022-03-12 13:11:34.222-0700',\n    \"2022-07-22'T'16:28:55.444\",\n    \"2022-09-08'T'03:13:10\",\n    \"2022-03-12'T'17:56:22'-0700'\",\n    \"2022-11-22'T'10:10:15.455\",\n    \"2022-02-11'T'18:31:44\",\n    '2022-10-30*02:47:33:899',\n    '2022-07-04*13:23:55',\n    '22-02-11 16:47:35,985 +0000',\n    '22-06-26 02:31:29,573',\n    '22-04-19 12:00:17',\n    '06/01/22 04:11:05',\n    '220423 11:42:35',\n    '20220423 11:42:35.173',\n    '08/10/22*13:33:56',\n    '11/22/2022*05:13:11',\n    '05/09/2022*08:22:14*612',\n    '04/23/22 04:34:22 +0000',\n    '10/03/2022 07:29:46 -0700',\n    '11:42:35',\n    '11:42:35.173',\n    '11:42:35,173',\n    '23/Apr 11:42:35,173',\n    '23/Apr/2022:11:42:35',\n    '23/Apr/2022 11:42:35',\n    '23-Apr-2022 11:42:35',\n    '23-Apr-2022 11:42:35.883',\n    '23 Apr 2022 11:42:35',\n    '23 Apr 2022 10:32:35*311',\n    '0423_11:42:35',\n    '0423_11:42:35.883',\n    '8/5/2022 3:31:18 AM:234',\n    '9/28/2022 2:23:15 PM',\n];\nconst dateTime = createTestGraph(id, type, values);\n\nexport default {\n    decimal,\n    dateTime,\n};\n"
  },
  {
    "path": "test/bdd/features/bid-suggestion.feature",
    "content": "@ignore\nFeature: Bid suggestion tests\n  # @ignore: dkg.js SDK removed network.getBidSuggestion() and assertion.getSizeInBytes()\n  # in v8. Re-enable once the SDK exposes a bid-suggestion API again.\n\n  Background: Setup local blockchain, bootstraps and nodes\n    Given the blockchains are set up\n    And 1 bootstrap is running\n\n  @bid-suggestion\n  Scenario: Get bid suggestion with a valid assertion\n    And I setup 2 additional nodes\n    And I wait for 15 seconds\n\n    When I call Get Bid Suggestion on the node 1 with validPublish_1ForValidUpdate_1 on blockchain hardhat1:31337\n    Then I call Info route on the node 1\n"
  },
  {
    "path": "test/bdd/features/get-errors.feature",
    "content": "Feature: Get errors test\n  Background: Setup local blockchain, bootstraps and nodes\n    Given the blockchains are set up\n    And 1 bootstrap is running\n\n  @ignore\n  Scenario: Getting non-existent UAL\n    # @ignore: A validly-formatted but non-existent UAL causes the node's get\n    # operation to stay IN_PROGRESS indefinitely while it searches the network.\n    # The operation never reaches a terminal status, so polling times out.\n    And I setup 1 additional node\n    And I wait for 15 seconds\n\n    When I call Get directly on the node 1 with nonExistentUAL on blockchain hardhat1:31337\n    And I wait for latest Get to finalize\n    Then Latest Get operation finished with status: FAILED\n\n  @get-error\n  Scenario: Getting invalid UAL\n    And I setup 1 additional node\n    And I wait for 15 seconds\n\n    When I call Get directly on the node 1 with invalidUAL on blockchain hardhat1:31337\n    And I wait for latest Get to finalize\n    Then Latest Get operation finished with status: GetRouteError\n"
  },
  {
    "path": "test/bdd/features/publish-errors.feature",
    "content": "Feature: Publish errors test\n  Background: Setup local blockchain, bootstraps and nodes\n    Given the blockchains are set up\n    And 1 bootstrap is running\n\n  @publish-error\n  Scenario: Publish a knowledge asset directly on the node with invalid request\n    And I setup 1 additional node\n    And I wait for 15 seconds\n\n    When I call Publish directly on the node 1 with validPublishRequestBody\n    And I wait for latest Publish to finalize\n    Then Latest Publish operation finished with status: HTTP_404\n"
  },
  {
    "path": "test/bdd/features/publish.feature",
    "content": "Feature: Publish related tests\n  Background: Setup local blockchain, bootstraps and nodes\n    Given the blockchains are set up\n    And 1 bootstrap is running\n\n  @smoke @publish\n  Scenario: Publishing a valid assertion\n    And I setup 1 additional node\n    And I wait for nodes to sync and mark active\n\n    When I call Publish on the node 1 with validAssertion on blockchain hardhat1:31337\n    And I wait for latest Publish to finalize\n    Then Latest Publish operation finished with status: COMPLETED\n\n  @publish @get\n  Scenario: Publish and retrieve a knowledge asset\n    And I setup 1 additional node\n    And I wait for nodes to sync and mark active\n\n    When I call Publish on the node 1 with validAssertion on blockchain hardhat1:31337\n    And I wait for latest Publish to finalize\n    Then Latest Publish operation finished with status: COMPLETED\n    And I wait for 10 seconds\n\n    When I get operation result from node 1 for latest published assertion\n    And I wait for latest resolve to finalize\n    Then Latest Get operation finished with status: COMPLETED\n"
  },
  {
    "path": "test/bdd/features/smoke.feature",
    "content": "Feature: Smoke tests — node health and basic operation\n  Background: Setup local blockchain, bootstraps and nodes\n    Given the blockchains are set up\n    And 1 bootstrap is running\n\n  @smoke\n  Scenario: Nodes start up and respond to the info route\n    And I setup 2 additional nodes\n    And I wait for 5 seconds\n    Then Node 1 responds to info route\n    And Node 2 responds to info route\n"
  },
  {
    "path": "test/bdd/features/update-errors.feature",
    "content": "Feature: Update errors test\n  Background: Setup local blockchain, bootstraps and nodes\n    Given the blockchains are set up\n    And 1 bootstrap is running\n\n  @update-error\n  Scenario: Update knowledge asset that was not previously published\n    And I setup 1 additional node\n    And I wait for 15 seconds\n\n    When I call Update directly on the node 1 with validUpdateRequestBody\n    And I wait for latest Update to finalize\n    Then Latest Update operation finished with status: HTTP_404\n"
  },
  {
    "path": "test/bdd/run-bdd.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nBLAZEGRAPH_JAR=\"${BLAZEGRAPH_JAR:-$HOME/blazegraph/blazegraph.jar}\"\nBLAZEGRAPH_PORT=9999\nBLAZEGRAPH_PID=\"\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nPROJECT_ROOT=\"$(cd \"$SCRIPT_DIR/../..\" && pwd)\"\n\ncleanup() {\n    echo \"\"\n    echo \"Cleaning up...\"\n    if [[ -n \"$BLAZEGRAPH_PID\" ]] && kill -0 \"$BLAZEGRAPH_PID\" 2>/dev/null; then\n        echo \"   Stopping Blazegraph (PID $BLAZEGRAPH_PID)\"\n        kill \"$BLAZEGRAPH_PID\" 2>/dev/null || true\n        wait \"$BLAZEGRAPH_PID\" 2>/dev/null || true\n    fi\n    echo \"Cleanup complete\"\n}\ntrap cleanup EXIT\n\n# -- Preflight checks ---------------------------------------------------------\n\necho \"Checking prerequisites...\"\n\nif ! command -v java &>/dev/null; then\n    echo \"ERROR: Java not found. Install a JDK (e.g. brew install --cask zulu17).\"\n    exit 1\nfi\n\nif [[ ! -f \"$BLAZEGRAPH_JAR\" ]]; then\n    echo \"ERROR: Blazegraph jar not found at $BLAZEGRAPH_JAR\"\n    echo \"   Set BLAZEGRAPH_JAR env var to the correct path.\"\n    exit 1\nfi\n\nif ! mysql -u root -e \"SELECT 1\" &>/dev/null; then\n    echo \"ERROR: MySQL is not running or root access failed.\"\n    echo \"   Start it with: brew services start mysql@8.0\"\n    exit 1\nfi\necho \"   OK: MySQL is reachable\"\n\nif ! redis-cli ping &>/dev/null; then\n    echo \"ERROR: Redis is not running.\"\n    echo \"   Start it with: brew services start redis\"\n    exit 1\nfi\necho \"   OK: Redis is reachable\"\n\n# -- Start Blazegraph ---------------------------------------------------------\n\nBLAZEGRAPH_ALREADY_RUNNING=false\nif curl -sf \"http://localhost:${BLAZEGRAPH_PORT}/blazegraph/status\" &>/dev/null; then\n    echo \"   OK: Blazegraph already running on port ${BLAZEGRAPH_PORT}\"\n    BLAZEGRAPH_ALREADY_RUNNING=true\nelse\n    echo \"   Starting Blazegraph on port ${BLAZEGRAPH_PORT}...\"\n    BLAZEGRAPH_DATA_DIR=\"/tmp/blazegraph-bdd-data\"\n    mkdir -p \"$BLAZEGRAPH_DATA_DIR\"\n    cd \"$BLAZEGRAPH_DATA_DIR\"\n    java -server -Xmx4g \"-Djetty.port=${BLAZEGRAPH_PORT}\" \\\n        -jar \"$BLAZEGRAPH_JAR\" &>/tmp/blazegraph-bdd.log &\n    BLAZEGRAPH_PID=$!\n    cd \"$PROJECT_ROOT\"\n\n    for i in $(seq 1 30); do\n        if curl -sf \"http://localhost:${BLAZEGRAPH_PORT}/blazegraph/status\" &>/dev/null; then\n            echo \"   OK: Blazegraph started (PID $BLAZEGRAPH_PID)\"\n            break\n        fi\n        if ! kill -0 \"$BLAZEGRAPH_PID\" 2>/dev/null; then\n            echo \"ERROR: Blazegraph process died. Check /tmp/blazegraph-bdd.log\"\n            exit 1\n        fi\n        sleep 2\n    done\n\n    if ! curl -sf \"http://localhost:${BLAZEGRAPH_PORT}/blazegraph/status\" &>/dev/null; then\n        echo \"ERROR: Blazegraph did not start within 60 seconds.\"\n        exit 1\n    fi\nfi\n\n# -- Run BDD tests ------------------------------------------------------------\n\necho \"\"\necho \"Running BDD tests...\"\necho \"\"\n\ncd \"$PROJECT_ROOT\"\n\nTEST_EXIT=0\nREPOSITORY_PASSWORD=\"${REPOSITORY_PASSWORD:-}\" npx cucumber-js \\\n    --config cucumber.js \\\n    --tags \"not @ignore\" \\\n    --format progress \\\n    --format-options '{\"colorsEnabled\": true}' \\\n    test/bdd/ \\\n    --import test/bdd/steps/ \\\n    --exit \\\n    \"$@\" || TEST_EXIT=$?\n\necho \"\"\nif [[ $TEST_EXIT -eq 0 ]]; then\n    echo \"All BDD tests passed!\"\nelse\n    echo \"Some BDD tests failed (exit code $TEST_EXIT)\"\nfi\n\n# If we started Blazegraph, stop it (handled by trap). If it was already\n# running, leave BLAZEGRAPH_PID empty so the trap doesn't kill it.\nif $BLAZEGRAPH_ALREADY_RUNNING; then\n    BLAZEGRAPH_PID=\"\"\nfi\n\nexit $TEST_EXIT\n"
  },
  {
    "path": "test/bdd/steps/api/bid-suggestion.mjs",
    "content": "import { When } from '@cucumber/cucumber';\nimport { expect, assert } from 'chai';\nimport { readFile } from 'fs/promises';\n\nconst assertions = JSON.parse(await readFile('test/bdd/steps/api/datasets/assertions.json'));\n\nWhen(\n    /^I call Get Bid Suggestion on node (\\d+) using parameters ([^\"]*), hashFunctionId (\\d+), scoreFunctionId (\\d+), within blockchain ([^\"]*)/,\n    { timeout: 300000 },\n    async function getBidSuggestionWithHashAndScore(\n        node,\n        assertionName,\n        hashFunctionId,\n        scoreFunctionId,\n        blockchain,\n    ) {\n        this.logger.log(\n            `I call get bid suggestion route on the node ${node} on blockchain ${blockchain} with hashFunctionId ${hashFunctionId} and scoreFunctionId ${scoreFunctionId}`,\n        );\n\n        expect(\n            !!this.state.localBlockchains[blockchain],\n            `Blockchain with name ${blockchain} not found`,\n        ).to.be.equal(true);\n\n        expect(\n            !!assertions[assertionName],\n            `Assertion with name: ${assertionName} not found!`,\n        ).to.be.equal(true);\n\n        expect(\n            Number.isInteger(hashFunctionId),\n            `hashFunctionId value: ${hashFunctionId} is not an integer!`,\n        ).to.be.equal(true);\n\n        expect(\n            Number.isInteger(scoreFunctionId),\n            `scoreFunctionId value: ${scoreFunctionId} is not an integer!`,\n        ).to.be.equal(true);\n\n        const assertion = assertions[assertionName];\n        const publicAssertionId = await this.state.nodes[node - 1].client\n            .getPublicAssertionId(assertion)\n            .catch((error) => {\n                assert.fail(`Error while trying to get public assertion id. ${error}`);\n            });\n\n        const sizeInBytes = Buffer.byteLength(JSON.stringify(assertion));\n\n        const options = {\n            ...this.state.nodes[node - 1].clientBlockchainOptions[blockchain],\n            hashFunctionId: hashFunctionId,\n            scoreFunctionId: scoreFunctionId,\n        };\n        let getBidSuggestionError;\n        const result = await this.state.nodes[node - 1].client\n            .getBidSuggestion(publicAssertionId, sizeInBytes, options)\n            .catch((error) => {\n                getBidSuggestionError = error;\n                assert.fail(`Error while trying to get bid suggestion. ${error}`);\n            });\n        this.state.latestBidSuggestionResult = {\n            nodeId: node - 1,\n            publicAssertionId,\n            sizeInBytes,\n            assertion: assertions[assertionName],\n            result,\n            getBidSuggestionError,\n        };\n    },\n);\n\nWhen(\n    /^I call Get Bid Suggestion on the node (\\d+) with ([^\"]*) on blockchain ([^\"]*)/,\n    { timeout: 300000 },\n    async function getBidSuggestion(node, assertionName, blockchain) {\n        this.logger.log(\n            `I call get bid suggestion route on the node ${node} on blockchain ${blockchain}`,\n        );\n\n        expect(\n            !!this.state.localBlockchains[blockchain],\n            `Blockchain with name ${blockchain} not found`,\n        ).to.be.equal(true);\n\n        expect(\n            !!assertions[assertionName],\n            `Assertion with name: ${assertionName} not found!`,\n        ).to.be.equal(true);\n\n        const assertion = assertions[assertionName];\n        const publicAssertionId = await this.state.nodes[node - 1].client\n            .getPublicAssertionId(assertion)\n            .catch((error) => {\n                assert.fail(`Error while trying to get public assertion id. ${error}`);\n            });\n\n        const sizeInBytes = Buffer.byteLength(JSON.stringify(assertion));\n\n        const options = this.state.nodes[node - 1].clientBlockchainOptions[blockchain];\n        let getBidSuggestionError;\n        const result = await this.state.nodes[node - 1].client\n            .getBidSuggestion(publicAssertionId, sizeInBytes, options)\n            .catch((error) => {\n                getBidSuggestionError = error;\n                assert.fail(`Error while trying to get bid suggestion. ${error}`);\n            });\n        this.state.latestBidSuggestionResult = {\n            nodeId: node - 1,\n            publicAssertionId,\n            sizeInBytes,\n            assertion: assertions[assertionName],\n            result,\n            getBidSuggestionError,\n        };\n    },\n);\n"
  },
  {
    "path": "test/bdd/steps/api/get.mjs",
    "content": "import { Then, When } from '@cucumber/cucumber';\nimport { expect, assert } from 'chai';\nimport { readFile } from 'fs/promises';\nimport HttpApiHelper from '../../../utilities/http-api-helper.mjs';\n\nconst requests = JSON.parse(await readFile('test/bdd/steps/api/datasets/requests.json'));\n\nconst httpApiHelper = new HttpApiHelper();\n\nWhen(\n    /^I call Get directly on the node (\\d+) with ([^\"]*) on blockchain ([^\"]*)/,\n    { timeout: 30000 },\n    async function getFromNode(node, requestName, blockchain) {\n        this.logger.log(`I call get directly on the node ${node} on blockchain ${blockchain}`);\n\n        expect(\n            !!this.state.localBlockchains[blockchain],\n            `Blockchain with name ${blockchain} not found`,\n        ).to.be.equal(true);\n\n        expect(\n            !!requests[requestName],\n            `Request body with name: ${requestName} not found!`,\n        ).to.be.equal(true);\n\n        const requestBody = JSON.parse(JSON.stringify(requests[requestName]));\n        requestBody.id = requestBody.id.replace('blockchain', blockchain);\n\n        try {\n            const result = await httpApiHelper.get(\n                this.state.nodes[node - 1].nodeRpcUrl,\n                requestBody,\n            );\n            const { operationId } = result.data;\n            this.state.latestGetData = {\n                nodeId: node - 1,\n                operationId,\n            };\n        } catch (error) {\n            this.state.latestError = error;\n        }\n    },\n);\n\nThen(/^It should fail with status code (\\d+)/, function checkLatestError(expectedStatusCode) {\n    const expectedStatusCodeInt = parseInt(expectedStatusCode, 10);\n    assert(this.state.latestError, 'No error occurred');\n    assert(this.state.latestError.statusCode, 'No status code in error');\n    assert(\n        this.state.latestError.statusCode === expectedStatusCodeInt,\n        `Expected request to fail with status code ${expectedStatusCodeInt}, but it failed with another code.`,\n    );\n});\n\nWhen('I wait for latest Get to finalize', { timeout: 120000 }, async function getFinalize() {\n    this.logger.log('I wait for latest get to finalize');\n    expect(\n        !!this.state.latestGetData,\n        'Latest get data is undefined. Get was not started.',\n    ).to.be.equal(true);\n\n    const { nodeId, operationId } = this.state.latestGetData;\n    this.logger.log(`Polling get result for operation id: ${operationId} on node: ${nodeId}`);\n\n    const result = await httpApiHelper.pollOperationResult(\n        this.state.nodes[nodeId].nodeRpcUrl,\n        'get',\n        operationId,\n        { intervalMs: 4000, maxRetries: 25 },\n    );\n\n    this.logger.log(`Get operation status: ${result.data.status}`);\n    this.state.latestGetData.result = result;\n    this.state.latestGetData.status = result.data.status;\n    this.state.latestGetData.errorType = result.data.data?.errorType;\n});\n\n"
  },
  {
    "path": "test/bdd/steps/api/info.mjs",
    "content": "import { When, Then } from '@cucumber/cucumber';\nimport assert from 'assert';\n\nWhen(/^I call Info route on the node (\\d+)/, { timeout: 120000 }, async function infoRouteCall(node) {\n    this.logger.log(`I call info route on the node ${node}`);\n    this.state.latestInfoData = await this.state.nodes[node - 1].client.info();\n});\n\nThen(/^The node version should start with number (\\d+)/, function checkNodeVersion(number) {\n    assert.ok(this.state.latestInfoData, 'No info response recorded — call the info route first');\n    assert.equal(\n        this.state.latestInfoData.version.startsWith(number),\n        true,\n        `Expected version to start with ${number}, got: ${this.state.latestInfoData.version}`,\n    );\n});\n"
  },
  {
    "path": "test/bdd/steps/api/publish.mjs",
    "content": "import { When } from '@cucumber/cucumber';\nimport { expect, assert } from 'chai';\nimport { readFile } from 'fs/promises';\nimport HttpApiHelper from '../../../utilities/http-api-helper.mjs';\n\nconst assertions = JSON.parse(await readFile('test/bdd/steps/api/datasets/assertions.json'));\nconst requests = JSON.parse(await readFile('test/bdd/steps/api/datasets/requests.json'));\n\nconst httpApiHelper = new HttpApiHelper();\n\nWhen(\n    /^I call Publish on the node (\\d+) with ([^\"]*) on blockchain ([^\"]*)/,\n    { timeout: 120000 },\n    async function publish(node, assertionName, blockchain) {\n        this.logger.log(`I call publish route on the node ${node} on blockchain ${blockchain}`);\n\n        expect(\n            !!this.state.localBlockchains[blockchain],\n            `Blockchain with name ${blockchain} not found`,\n        ).to.be.equal(true);\n\n        expect(\n            !!assertions[assertionName],\n            `Assertion with name: ${assertionName} not found!`,\n        ).to.be.equal(true);\n\n        const assertion = assertions[assertionName];\n        const options = this.state.nodes[node - 1].clientBlockchainOptions[blockchain];\n        const result = await this.state.nodes[node - 1].client\n            .publish(assertion, options)\n            .catch((error) => {\n                assert.fail(`Error while trying to publish assertion. ${error}`);\n            });\n\n        const publishOp = result.operation?.publish ?? {};\n        this.state.latestPublishData = {\n            nodeId: node - 1,\n            UAL: result.UAL,\n            operationId: publishOp.operationId,\n            assertion: assertions[assertionName],\n            status: publishOp.status || 'PENDING',\n            errorType: publishOp.errorType,\n            result,\n        };\n    },\n);\n\nWhen(\n    /^I call Publish directly on the node (\\d+) with ([^\"]*)/,\n    { timeout: 70000 },\n    async function publishDirect(node, requestName) {\n        this.logger.log(`I call publish on the node ${node} directly`);\n        expect(\n            !!requests[requestName],\n            `Request body with name: ${requestName} not found!`,\n        ).to.be.equal(true);\n        const requestBody = requests[requestName];\n        try {\n            const result = await httpApiHelper.publish(\n                this.state.nodes[node - 1].nodeRpcUrl,\n                requestBody,\n            );\n            const { operationId } = result.data;\n            this.state.latestPublishData = {\n                nodeId: node - 1,\n                operationId,\n            };\n        } catch (error) {\n            this.state.latestPublishData = {\n                nodeId: node - 1,\n                status: 'FAILED',\n                errorType: error.statusCode ? `HTTP_${error.statusCode}` : 'FAILED',\n            };\n        }\n    },\n);\n\nWhen('I wait for latest Publish to finalize', { timeout: 120000 }, async function publishFinalize() {\n    this.logger.log('I wait for latest publish to finalize');\n    expect(\n        !!this.state.latestPublishData,\n        'Latest publish data is undefined. Publish was not started.',\n    ).to.be.equal(true);\n\n    const { nodeId, operationId, status } = this.state.latestPublishData;\n\n    if (!operationId) {\n        this.logger.log(`No operationId to poll, using existing status: ${status}`);\n        return;\n    }\n\n    this.logger.log(`Polling publish result for operation id: ${operationId} on node: ${nodeId}`);\n\n    const result = await httpApiHelper.pollOperationResult(\n        this.state.nodes[nodeId].nodeRpcUrl,\n        'publish',\n        operationId,\n        { intervalMs: 5000, maxRetries: 20 },\n    );\n\n    this.logger.log(`Publish operation status: ${result.data.status}`);\n    this.state.latestPublishData.result = result;\n    this.state.latestPublishData.status = result.data.status;\n    this.state.latestPublishData.errorType = result.data.data?.errorType;\n});\n\n"
  },
  {
    "path": "test/bdd/steps/api/resolve.mjs",
    "content": "import { When } from '@cucumber/cucumber';\nimport { expect, assert } from 'chai';\nimport HttpApiHelper from '../../../utilities/http-api-helper.mjs';\n\nconst httpApiHelper = new HttpApiHelper();\n\nWhen(\n    /^I get operation result from node (\\d+) for latest published assertion/,\n    { timeout: 120000 },\n    async function resolveCall(node) {\n        this.logger.log('I call get result for the latest operation');\n        expect(\n            !!this.state.latestPublishData,\n            'Latest publish data is undefined. Publish is not finalized.',\n        ).to.be.equal(true);\n\n        try {\n            const result = await this.state.nodes[node - 1].client\n                .get(this.state.latestPublishData.UAL)\n                .catch((error) => {\n                    assert.fail(`Error while trying to resolve assertion. ${error}`);\n                });\n\n            const getOp = result.operation?.get ?? result.operation ?? {};\n            const hasData = !!(result.assertion || result.public || result.data);\n\n            // The SDK's asset.get() completes the full get flow internally.\n            // If it returned with an errorType, the operation failed.\n            // If it returned assertion data OR has no operationId to poll,\n            // the operation completed successfully inside the SDK.\n            let resolvedStatus = getOp.status || 'PENDING';\n            if (getOp.errorType) {\n                resolvedStatus = 'FAILED';\n            } else if (hasData || !getOp.operationId) {\n                resolvedStatus = 'COMPLETED';\n            }\n\n            this.state.latestGetData = {\n                nodeId: node - 1,\n                operationId: getOp.operationId,\n                result,\n                status: resolvedStatus,\n                errorType: getOp.errorType,\n            };\n        } catch (e) {\n            this.logger.log(`Error while getting operation result: ${e}`);\n            this.state.latestGetData = {\n                nodeId: node - 1,\n                status: 'FAILED',\n            };\n        }\n    },\n);\n\nWhen(\n    'I wait for latest resolve to finalize',\n    { timeout: 120000 },\n    async function resolveFinalizeCall() {\n        this.logger.log('I wait for latest resolve to finalize');\n        expect(\n            !!this.state.latestGetData,\n            'Latest resolve data is undefined. Resolve is not started.',\n        ).to.be.equal(true);\n\n        const { nodeId, operationId, status } = this.state.latestGetData;\n\n        if (!operationId || (status && ['COMPLETED', 'FAILED'].includes(status))) {\n            this.logger.log(\n                `Resolve already finalized (status: ${status}, operationId: ${operationId})`,\n            );\n            return;\n        }\n\n        this.logger.log(\n            `Polling resolve result for operation id: ${operationId} on node: ${nodeId}`,\n        );\n\n        const result = await httpApiHelper.pollOperationResult(\n            this.state.nodes[nodeId].nodeRpcUrl,\n            'get',\n            operationId,\n            { intervalMs: 4000, maxRetries: 25 },\n        );\n\n        this.logger.log(`Resolve operation status: ${result.data.status}`);\n        this.state.latestGetData.result = result;\n        this.state.latestGetData.status = result.data.status;\n        this.state.latestGetData.errorType = result.data.data?.errorType;\n    },\n);\n"
  },
  {
    "path": "test/bdd/steps/api/update.mjs",
    "content": "import { When } from '@cucumber/cucumber';\nimport { expect } from 'chai';\nimport { readFile } from 'fs/promises';\nimport HttpApiHelper from '../../../utilities/http-api-helper.mjs';\n\nconst requests = JSON.parse(await readFile('test/bdd/steps/api/datasets/requests.json'));\n\nconst httpApiHelper = new HttpApiHelper();\n\nWhen(\n    /^I call Update directly on the node (\\d+) with ([^\"]*)/,\n    { timeout: 70000 },\n    async function updateDirect(node, requestName) {\n        this.logger.log(`I call update on the node ${node} directly`);\n        expect(\n            !!requests[requestName],\n            `Request body with name: ${requestName} not found!`,\n        ).to.be.equal(true);\n        const requestBody = requests[requestName];\n        try {\n            const result = await httpApiHelper.update(\n                this.state.nodes[node - 1].nodeRpcUrl,\n                requestBody,\n            );\n            const { operationId } = result.data;\n            this.state.latestUpdateData = {\n                nodeId: node - 1,\n                operationId,\n            };\n        } catch (error) {\n            this.state.latestUpdateData = {\n                nodeId: node - 1,\n                status: 'FAILED',\n                errorType: error.statusCode ? `HTTP_${error.statusCode}` : 'FAILED',\n            };\n        }\n    },\n);\n\nWhen('I wait for latest Update to finalize', { timeout: 120000 }, async function updateFinalize() {\n    this.logger.log('I wait for latest update to finalize');\n    expect(\n        !!this.state.latestUpdateData,\n        'Latest update data is undefined. Update was not started.',\n    ).to.be.equal(true);\n\n    const { nodeId, operationId, status } = this.state.latestUpdateData;\n\n    if (!operationId) {\n        this.logger.log(`No operationId to poll, using existing status: ${status}`);\n        return;\n    }\n\n    this.logger.log(`Polling update result for operation id: ${operationId} on node: ${nodeId}`);\n\n    const result = await httpApiHelper.pollOperationResult(\n        this.state.nodes[nodeId].nodeRpcUrl,\n        'update',\n        operationId,\n        { intervalMs: 5000, maxRetries: 20 },\n    );\n\n    this.logger.log(`Update operation status: ${result.data.status}`);\n    this.state.latestUpdateData.result = result;\n    this.state.latestUpdateData.status = result.data.status;\n    this.state.latestUpdateData.errorType = result.data.data?.errorType;\n});\n\n"
  },
  {
    "path": "test/bdd/steps/blockchain.mjs",
    "content": "import { Given } from '@cucumber/cucumber';\nimport fs from 'fs';\nimport LocalBlockchain from './lib/local-blockchain.mjs';\n\nconst BLOCKCHAIN_CONFIGS = [\n    { name: 'hardhat1:31337', port: 8545 },\n    { name: 'hardhat2:31337', port: 9545 },\n];\n\nGiven(/^the blockchains are set up$/, { timeout: 240_000 }, async function blockchainSetup() {\n    await Promise.all(\n        BLOCKCHAIN_CONFIGS.map(({ name, port }) => {\n            this.logger.log(`Starting local blockchain ${name} on port: ${port}`);\n            const blockchainConsole = new console.Console(\n                fs.createWriteStream(\n                    `${this.state.scenarioLogDir}/blockchain-${name.replace(':', '-')}.log`,\n                ),\n            );\n            const localBlockchain = new LocalBlockchain();\n            this.state.localBlockchains[name] = localBlockchain;\n            return localBlockchain.initialize(port, blockchainConsole);\n        }),\n    );\n\n    // The on-chain default minimumRequiredSignatures is 3, which requires 3 nodes in the\n    // shard before a publish can succeed. Lower it to 2 so our small BDD network (1 bootstrap\n    // + 2 regular nodes) can publish without running into \"Unable to find enough nodes\".\n    // Lower the on-chain minimumRequiredSignatures for our small BDD network.\n    // The ShardingTableCheckCommand syncs the on-chain sharding table into each node's\n    // local DB every 10 seconds, so nodes may not see each other's profiles yet when a\n    // publish arrives. Setting this to 1 ensures the publishing node itself (always in\n    // its own shard) satisfies the requirement.\n    for (const blockchain of Object.values(this.state.localBlockchains)) {\n        await blockchain.setParametersStorageParams({ minimumRequiredSignatures: 1 });\n    }\n});\n"
  },
  {
    "path": "test/bdd/steps/common.mjs",
    "content": "import { execSync } from 'child_process';\nimport { Given, Then } from '@cucumber/cucumber';\nimport { expect, assert } from 'chai';\nimport fs from 'fs';\nimport path from 'path';\nimport { setTimeout as sleep } from 'timers/promises';\nimport mysql from 'mysql2';\n\nimport DkgClientHelper from '../../utilities/dkg-client-helper.mjs';\nimport StepsUtils, {\n    BOOTSTRAP_NETWORK_PORT,\n    BOOTSTRAP_RPC_PORT,\n} from '../../utilities/steps-utils.mjs';\nimport FileService from '../../../src/service/file-service.js';\n\nconst stepsUtils = new StepsUtils();\n\nGiven(\n    /^I setup (\\d+)[ additional]* node[s]*$/,\n    { timeout: 60000 },\n    async function nodeSetup(nodeCount) {\n        this.logger.log(`I setup ${nodeCount} node${nodeCount !== 1 ? 's' : ''}`);\n\n        const currentNumberOfNodes = Object.keys(this.state.nodes).length;\n\n        await Promise.all(\n            Array.from({ length: nodeCount }, (_, i) => {\n                const nodeIndex = currentNumberOfNodes + i;\n                // wallets[0] is reserved for the bootstrap node; regular nodes start from index 1\n                const walletIndex = nodeIndex + 1;\n                const blockchains = Object.entries(this.state.localBlockchains).map(\n                    ([blockchainId, blockchain]) => {\n                        const wallets = blockchain.getWallets();\n                        return {\n                            blockchainId,\n                            operationalWallet: wallets[walletIndex],\n                            managementWallet: wallets[walletIndex + Math.floor(wallets.length / 2)],\n                            port: blockchain.port,\n                        };\n                    },\n                );\n\n                const rpcPort = 8901 + nodeIndex;\n                const networkPort = 9001 + nodeIndex;\n                const nodeName = `origintrail-test-${nodeIndex}`;\n                const nodeConfiguration = stepsUtils.createNodeConfiguration(\n                    blockchains,\n                    nodeIndex,\n                    nodeName,\n                    rpcPort,\n                    networkPort,\n                    false,\n                    this.state.bootstrapPeerMultiaddr,\n                );\n\n                // Remove stale data from any interrupted prior run so the node starts clean\n                fs.rmSync(path.join(process.cwd(), nodeConfiguration.appDataPath), {\n                    recursive: true,\n                    force: true,\n                });\n\n                const forkedNode = stepsUtils.forkNode(nodeConfiguration);\n\n                // Track immediately so the After hook can kill it even if the step times out\n                // before the process sends STARTED.\n                this.state.pendingProcesses.push(forkedNode);\n\n                const logFileStream = fs.createWriteStream(\n                    `${this.state.scenarioLogDir}/${nodeName}.log`,\n                );\n                forkedNode.stdout.setEncoding('utf8');\n                forkedNode.stdout.on('data', (data) => logFileStream.write(data));\n                forkedNode.stderr.setEncoding('utf8');\n                forkedNode.stderr.on('data', (data) => logFileStream.write(`[stderr] ${data}`));\n\n                return new Promise((resolve, reject) => {\n                    let settled = false;\n                    const done = (fn, ...args) => {\n                        if (!settled) {\n                            settled = true;\n                            fn(...args);\n                        }\n                    };\n                    const removePending = () => {\n                        const idx = this.state.pendingProcesses.indexOf(forkedNode);\n                        if (idx !== -1) this.state.pendingProcesses.splice(idx, 1);\n                    };\n\n                    forkedNode.on('error', (err) => {\n                        removePending();\n                        done(reject, err);\n                    });\n                    forkedNode.on('exit', (code, signal) => {\n                        removePending();\n                        done(\n                            reject,\n                            new Error(\n                                `Node ${nodeIndex} process exited with code=${code} signal=${signal} before sending STARTED`,\n                            ),\n                        );\n                    });\n                    forkedNode.on('message', (response) => {\n                        if (response.error) {\n                            // Process reported an error - keep in pendingProcesses for cleanup\n                            done(\n                                reject,\n                                new Error(\n                                    `Error initializing node ${nodeIndex}: ${response.error}`,\n                                ),\n                            );\n                            return;\n                        }\n\n                        try {\n                            const [[firstBlockchainId, firstBlockchain]] = Object.entries(\n                                this.state.localBlockchains,\n                            );\n                            const firstWallets = firstBlockchain.getWallets();\n\n                            const client = new DkgClientHelper({\n                                endpoint: 'http://localhost',\n                                port: rpcPort,\n                                blockchain: {\n                                    name: firstBlockchainId,\n                                    publicKey: firstWallets[walletIndex].address,\n                                    privateKey: firstWallets[walletIndex].privateKey,\n                                    rpc: `http://localhost:${firstBlockchain.port}`,\n                                    hubContract:\n                                        '0x5FbDB2315678afecb367f032d93F642f64180aa3',\n                                },\n                                maxNumberOfRetries: 20,\n                                frequency: 5,\n                                contentType: 'all',\n                            });\n\n                            const clientBlockchainOptions = {};\n                            Object.entries(this.state.localBlockchains).forEach(\n                                ([blockchainId, blockchain]) => {\n                                    const wallets = blockchain.getWallets();\n                                    clientBlockchainOptions[blockchainId] = {\n                                        blockchain: {\n                                            name: blockchainId,\n                                            publicKey: wallets[walletIndex].address,\n                                            privateKey: wallets[walletIndex].privateKey,\n                                            rpc: `http://localhost:${blockchain.port}`,\n                                            hubContract:\n                                                '0x5FbDB2315678afecb367f032d93F642f64180aa3',\n                                        },\n                                    };\n                                },\n                            );\n\n                            this.state.nodes[nodeIndex] = {\n                                client,\n                                forkedNode,\n                                configuration: nodeConfiguration,\n                                nodeRpcUrl: `http://localhost:${rpcPort}`,\n                                fileService: new FileService({\n                                    config: nodeConfiguration,\n                                    logger: this.logger,\n                                }),\n                                clientBlockchainOptions,\n                            };\n\n                            // Registration succeeded — safe to remove from pending tracking\n                            removePending();\n                            done(resolve);\n                        } catch (err) {\n                            // Registration failed — keep in pendingProcesses so After hook can kill it\n                            done(reject, err);\n                        }\n                    });\n                });\n            }),\n        );\n    },\n);\n\nGiven(\n    /^(\\d+) bootstrap is running$/,\n    { timeout: 60000 },\n    async function bootstrapRunning(nodeCount) {\n        expect(this.state.bootstraps).to.have.length(0);\n        expect(nodeCount).to.be.equal(1); // only one supported currently\n\n        this.logger.log('Initializing bootstrap node');\n\n        const portOffset = Math.floor(Math.random() * 1000);\n        const rpcPort = BOOTSTRAP_RPC_PORT + portOffset;\n        const networkPort = BOOTSTRAP_NETWORK_PORT + portOffset;\n\n        for (const port of [rpcPort, networkPort]) {\n            try {\n                execSync(`npx kill-port --port ${port}`, { stdio: 'ignore' });\n            } catch {\n                // Port may already be free\n            }\n        }\n\n        this.state.bootstrapPeerMultiaddr = `/ip4/127.0.0.1/tcp/${networkPort}/p2p/QmWyf3dtqJnhuCpzEDTNmNFYc5tjxTrXhGcUUmGHdg2gtj`;\n\n        const blockchains = Object.entries(this.state.localBlockchains).map(\n            ([blockchainId, blockchain]) => ({\n                blockchainId,\n                operationalWallet: blockchain.getWallets()[0],\n                managementWallet: blockchain.getWallets()[Math.floor(blockchain.getWallets().length / 2)],\n                port: blockchain.port,\n            }),\n        );\n\n        const nodeName = 'origintrail-test-bootstrap';\n        const nodeConfiguration = stepsUtils.createNodeConfiguration(\n            blockchains,\n            0, // bootstrap always uses wallet index 0\n            nodeName,\n            rpcPort,\n            networkPort,\n            true, // bootstrap=true: fixed libp2p key, isolated DB/data paths\n        );\n        this.state.bootstrapRpcPort = rpcPort;\n\n        // Clear any stale data from a previously failed run before starting\n        fs.rmSync(path.join(process.cwd(), nodeConfiguration.appDataPath), {\n            recursive: true,\n            force: true,\n        });\n\n        const forkedNode = stepsUtils.forkNode(nodeConfiguration);\n\n        // Track immediately so the After hook can kill it even if the step times out\n        // before the process sends STARTED.\n        this.state.pendingProcesses.push(forkedNode);\n\n        const logFileStream = fs.createWriteStream(\n            `${this.state.scenarioLogDir}/${nodeName}.log`,\n        );\n        forkedNode.stdout.setEncoding('utf8');\n        forkedNode.stdout.on('data', (data) => logFileStream.write(data));\n        forkedNode.stderr.setEncoding('utf8');\n        forkedNode.stderr.on('data', (data) => logFileStream.write(`[stderr] ${data}`));\n\n        await new Promise((resolve, reject) => {\n            let settled = false;\n            const done = (fn, ...args) => {\n                if (!settled) {\n                    settled = true;\n                    fn(...args);\n                }\n            };\n            const removePending = () => {\n                const idx = this.state.pendingProcesses.indexOf(forkedNode);\n                if (idx !== -1) this.state.pendingProcesses.splice(idx, 1);\n            };\n\n            forkedNode.on('error', (err) => {\n                removePending();\n                done(reject, err);\n            });\n            forkedNode.on('exit', (code, signal) => {\n                removePending();\n                done(\n                    reject,\n                    new Error(\n                        `Bootstrap process exited with code=${code} signal=${signal} before sending STARTED`,\n                    ),\n                );\n            });\n            forkedNode.on('message', (response) => {\n                if (response.error) {\n                    // Process reported an error — keep in pendingProcesses for cleanup\n                    done(\n                        reject,\n                        new Error(`Error initializing bootstrap node: ${response.error}`),\n                    );\n                    return;\n                }\n\n                try {\n                    const [[firstBlockchainId, firstBlockchain]] = Object.entries(\n                        this.state.localBlockchains,\n                    );\n\n                    const client = new DkgClientHelper({\n                        endpoint: 'http://localhost',\n                        port: rpcPort,\n                        blockchain: {\n                            name: firstBlockchainId,\n                            publicKey: firstBlockchain.getWallets()[0].address,\n                            privateKey: firstBlockchain.getWallets()[0].privateKey,\n                            rpc: `http://localhost:${firstBlockchain.port}`,\n                            hubContract: '0x5FbDB2315678afecb367f032d93F642f64180aa3',\n                        },\n                        useSSL: false,\n                        timeout: 25,\n                        loglevel: 'trace',\n                    });\n\n                    this.state.bootstraps.push({\n                        client,\n                        forkedNode,\n                        configuration: nodeConfiguration,\n                        nodeRpcUrl: `http://localhost:${rpcPort}`,\n                        fileService: new FileService({\n                            config: nodeConfiguration,\n                            logger: this.logger,\n                        }),\n                    });\n\n                    // Registration succeeded — safe to remove from pending tracking\n                    removePending();\n                    done(resolve);\n                } catch (err) {\n                    // Registration failed — keep in pendingProcesses so After hook can kill it\n                    done(reject, err);\n                }\n            });\n        });\n    },\n);\n\nThen(\n    /Latest (Get|Publish|Update) operation finished with status: (\\S+)$/,\n    { timeout: 120000 },\n    async function latestOperationFinished(operationName, status) {\n        this.logger.log(`Latest ${operationName} operation finished with status: ${status}`);\n        const operationData = `latest${operationName}Data`;\n        expect(\n            !!this.state[operationData],\n            `Latest ${operationName} result is undefined. ${operationData} result not started.`,\n        ).to.be.equal(true);\n        expect(\n            !!(this.state[operationData].result || this.state[operationData].status),\n            `Latest ${operationName} has no result or status. ${operationData} is not finished.`,\n        ).to.be.equal(true);\n\n        expect(\n            this.state[operationData].errorType ?? this.state[operationData].status,\n            `${operationData} result status validation failed`,\n        ).to.be.equal(status);\n    },\n);\n\nGiven(/^I wait for (\\d+) seconds$/, { timeout: 100000 }, async function waitFor(seconds) {\n    this.logger.log(`I wait for ${seconds} seconds`);\n    await sleep(seconds * 1000);\n});\n\n/**\n * Deterministic wait for the sharding table to be populated and peers marked active.\n *\n * The publish pipeline needs shard records to exist before it can find replication peers.\n * ShardingTableCheckCommand creates them every ~10 s, but only when the on-chain count\n * differs from the local count. This step polls until all expected records are present,\n * then stamps them with the current time so DialPeersCommand doesn't needlessly re-dial\n * healthy peers whose lastDialed is still the epoch default.\n */\nGiven(\n    /^I wait for nodes to sync and mark active$/,\n    { timeout: 30000 },\n    async function waitForSyncAndActivate() {\n        const expectedPeerCount =\n            this.state.bootstraps.length + Object.keys(this.state.nodes).length;\n\n        const allNodes = [...this.state.bootstraps, ...Object.values(this.state.nodes)];\n        const dbNames = allNodes.map((n) => n.configuration.operationalDatabase.databaseName);\n\n        const con = mysql.createConnection({\n            host: 'localhost',\n            user: 'root',\n            password: process.env.REPOSITORY_PASSWORD,\n        });\n\n        // Poll until shard records appear in every node's DB\n        const maxAttempts = 12;\n        for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n            let allSynced = true;\n            for (const db of dbNames) {\n                try {\n                    // eslint-disable-next-line no-await-in-loop\n                    const [rows] = await con\n                        .promise()\n                        .query(`SELECT COUNT(*) AS cnt FROM \\`${db}\\`.shard`);\n                    if (rows[0].cnt < expectedPeerCount) {\n                        allSynced = false;\n                        break;\n                    }\n                } catch {\n                    allSynced = false;\n                    break;\n                }\n            }\n            if (allSynced) {\n                this.logger.log(\n                    `Sharding table synced after ${attempt * 2}s (${expectedPeerCount} peers)`,\n                );\n                break;\n            }\n            if (attempt === maxAttempts) {\n                this.logger.log(\n                    'Warning: sharding table may not have fully synced within the timeout',\n                );\n            }\n            // eslint-disable-next-line no-await-in-loop\n            await sleep(2000);\n        }\n\n        // Stamp fresh records with current time so that:\n        //  1. filterInactive (WHERE last_seen = last_dialed) keeps passing\n        //  2. DialPeersCommand doesn't waste cycles re-dialing perfectly healthy peers\n        //     whose lastDialed is still the epoch default (Date(0))\n        for (const db of dbNames) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                await con\n                    .promise()\n                    .query(`UPDATE \\`${db}\\`.shard SET last_seen = NOW(), last_dialed = NOW()`);\n            } catch (e) {\n                this.logger.log(`Warning: could not update shard in ${db}: ${e.message}`);\n            }\n        }\n\n        con.end();\n    },\n);\n\nGiven(/^Node (\\d+) responds to info route$/, { timeout: 30000 }, async function (nodeNumber) {\n    const nodeIndex = parseInt(nodeNumber, 10) - 1;\n    const MAX_RETRIES = 10;\n    let response;\n    for (let i = 0; i < MAX_RETRIES; i += 1) {\n        try {\n            // eslint-disable-next-line no-await-in-loop\n            response = await this.state.nodes[nodeIndex].client.info();\n            break;\n        } catch {\n            // eslint-disable-next-line no-await-in-loop\n            await sleep(2000);\n        }\n    }\n\n    this.logger.log(`Node ${nodeNumber} info response: ${JSON.stringify(response)}`);\n\n    assert.ok(response && response.version, 'Expected node info to contain \"version\" field');\n});\n"
  },
  {
    "path": "test/bdd/steps/hooks.mjs",
    "content": "import 'dotenv/config';\nimport { execSync } from 'child_process';\nimport { setTimeout } from 'timers/promises';\nimport { Before, BeforeAll, After, AfterAll } from '@cucumber/cucumber';\nimport slugify from 'slugify';\nimport fs from 'fs';\nimport mysql from 'mysql2';\nimport { NODE_ENVIRONMENTS } from '../../../src/constants/constants.js';\nimport TripleStoreModuleManager from '../../../src/modules/triple-store/triple-store-module-manager.js';\n\n/** Delay after killing node processes so the OS releases ports before the next scenario/retry. */\nconst PORT_RELEASE_DELAY_MS = 2500;\n\nprocess.env.NODE_ENV = NODE_ENVIRONMENTS.TEST;\n\nBeforeAll(() => {});\n\nBefore(async function beforeMethod(testCase) {\n    this.logger = console;\n    this.logger.log('\\n🟡 Starting scenario:', testCase.pickle.name);\n\n    this.state = {\n        localBlockchains: {},\n        nodes: {},\n        bootstraps: [],\n        pendingProcesses: [],\n    };\n\n    // Flush Redis to remove stale BullMQ queues/jobs from prior scenarios.\n    // Each node uses a per-node queue name (command-executor-node0, etc.); without\n    // flushing, old job schedulers and pending jobs survive across scenarios.\n    try {\n        execSync('redis-cli FLUSHALL', { stdio: 'ignore' });\n    } catch {\n        // Non-fatal: Redis may not have stale data\n    }\n\n    // Drop stale databases from prior crashed runs so nodes start clean on first attempt\n    try {\n        const con = mysql.createConnection({\n            host: 'localhost',\n            user: 'root',\n            password: process.env.REPOSITORY_PASSWORD,\n        });\n        const staleDbNames = [\n            'operationaldbbootstrap',\n            ...Array.from({ length: 10 }, (_, i) => `operationaldbnode${i}`),\n        ];\n        for (const db of staleDbNames) {\n            await con.promise().query(`DROP DATABASE IF EXISTS \\`${db}\\`;`);\n        }\n        con.end();\n    } catch {\n        // Non-fatal: node will attempt to create the DB itself\n    }\n\n    let logDir = process.env.CUCUMBER_ARTIFACTS_DIR || '.';\n    logDir += `/test/bdd/log/${slugify(testCase.pickle.name)}`;\n    fs.mkdirSync(logDir, { recursive: true });\n    this.state.scenarioLogDir = logDir;\n    this.logger.log('📁 Scenario logs:', logDir);\n});\n\nAfter({ timeout: 60000 }, async function afterMethod(testCase) {\n    const tripleStoreConfiguration = [];\n    const databaseNames = [];\n    const promises = [];\n\n    // SIGKILL all node processes so they are terminated immediately without waiting for\n    // async cleanup that could hang (e.g. trying to close blockchain connections to an\n    // already-stopped Hardhat instance). This guarantees all ports are released before\n    // the next scenario (or retry) starts.\n    for (const proc of this.state.pendingProcesses) {\n        proc.kill('SIGKILL');\n    }\n\n    const allNodes = [...Object.values(this.state.nodes), ...this.state.bootstraps];\n    for (const node of allNodes) {\n        node.forkedNode.kill('SIGKILL');\n\n        const tripleStoreModuleConfig = node.configuration.modules.tripleStore;\n        const OT_BLAZEGRAPH_PACKAGE =\n            './triple-store/implementation/ot-blazegraph/ot-blazegraph.js';\n        const enabledTripleStore = {\n            enabled: true,\n            implementation: {},\n        };\n        for (const [implName, implConfig] of Object.entries(\n            tripleStoreModuleConfig.implementation || {},\n        )) {\n            enabledTripleStore.implementation[implName] = {\n                ...implConfig,\n                enabled: true,\n                package: implConfig.package || OT_BLAZEGRAPH_PACKAGE,\n            };\n        }\n        tripleStoreConfiguration.push({\n            appDataPath: node.configuration.appDataPath,\n            modules: { tripleStore: enabledTripleStore },\n        });\n        databaseNames.push(node.configuration.operationalDatabase.databaseName);\n        promises.push(node.fileService.removeFolder(node.fileService.getDataFolderPath()));\n    }\n\n    await setTimeout(PORT_RELEASE_DELAY_MS);\n\n    for (const [blockchainId, blockchain] of Object.entries(this.state.localBlockchains)) {\n        this.logger.log(`🛑 Stopping local blockchain ${blockchainId}`);\n        promises.push(blockchain.stop());\n    }\n\n    this.logger.log('🧹 Cleaning up repositories and databases...');\n    const con = mysql.createConnection({\n        host: 'localhost',\n        user: 'root',\n        password: process.env.REPOSITORY_PASSWORD,\n    });\n\n    for (const db of databaseNames) {\n        const sql = `DROP DATABASE IF EXISTS \\`${db}\\`;`;\n        promises.push(con.promise().query(sql));\n    }\n\n    for (const tsConfig of tripleStoreConfiguration) {\n        promises.push(\n            (async () => {\n                const tripleStoreModuleManager = new TripleStoreModuleManager({\n                    config: tsConfig,\n                    logger: this.logger,\n                });\n                await tripleStoreModuleManager.initialize();\n                for (const impl of tripleStoreModuleManager.getImplementationNames()) {\n                    const { config: implConfig } =\n                        tripleStoreModuleManager.getImplementation(impl);\n                    if (!implConfig?.repositories) continue;\n                    for (const repo of Object.keys(implConfig.repositories)) {\n                        this.logger.log('🗑 Removing triple store repository:', repo);\n                        await tripleStoreModuleManager.deleteRepository(impl, repo);\n                    }\n                }\n            })(),\n        );\n    }\n\n    await Promise.all(promises);\n    con.end();\n\n    this.logger.log('\\n✅ Completed scenario:', testCase.pickle.name);\n    this.logger.log(\n        `📄 Location: ${testCase.gherkinDocument.uri}:${testCase.gherkinDocument.feature.location.line}`,\n    );\n    this.logger.log(`🟢 Status: ${testCase.result.status}`);\n    const durationMs = testCase.result.duration\n        ? (Number(testCase.result.duration.seconds) || 0) * 1000 +\n          (Number(testCase.result.duration.nanos) || 0) / 1e6\n        : 0;\n    this.logger.log(`⏱ Duration: ${Math.round(durationMs)} ms\\n`);\n});\n\nAfterAll(async () => {});\n\nprocess.on('unhandledRejection', (reason) => {\n    console.error('Unhandled rejection in test runner:', reason);\n    process.abort();\n});\n"
  },
  {
    "path": "test/bdd/steps/lib/local-blockchain.mjs",
    "content": "import { ethers } from 'ethers';\nimport { readFile } from 'fs/promises';\nimport { exec, execSync } from 'child_process';\n\nconst Hub = JSON.parse((await readFile('node_modules/dkg-evm-module/abi/Hub.json')).toString());\nconst ParametersStorage = JSON.parse(\n    (await readFile('node_modules/dkg-evm-module/abi/ParametersStorage.json')).toString(),\n);\n\nconst hubContractAddress = '0x5FbDB2315678afecb367f032d93F642f64180aa3';\n\n/**\n * LocalBlockchain wraps a local Hardhat node process for BDD testing.\n *\n * Starts a Hardhat chain via `npm run start:local_blockchain -- <port>`,\n * connects an ethers provider, loads predefined test wallets, and exposes\n * helpers to mutate on-chain ParametersStorage values during scenarios.\n *\n * Basic usage:\n *   const localBlockchain = new LocalBlockchain();\n *   await localBlockchain.initialize(8545, console);\n *   // use localBlockchain.getWallets(), setR0(), setR1(), etc.\n *   await localBlockchain.stop();\n */\nclass LocalBlockchain {\n    async initialize(port, _console = console, version = '') {\n        this.port = port;\n        this.startBlockchainProcess = exec(\n            `npm run start:local_blockchain${version} -- ${port}`,\n        );\n        this.startBlockchainProcess.stdout.on('data', (data) => {\n            _console.log(data);\n        });\n\n        this.provider = new ethers.providers.JsonRpcProvider(`http://localhost:${port}`);\n\n        const [privateKeysFile, publicKeysFile] = await Promise.all([\n            readFile('test/bdd/steps/api/datasets/privateKeys.json'),\n            readFile('test/bdd/steps/api/datasets/publicKeys.json'),\n        ]);\n\n        const privateKeys = JSON.parse(privateKeysFile.toString());\n        const publicKeys = JSON.parse(publicKeysFile.toString());\n\n        this.wallets = privateKeys.map((privateKey, index) => ({\n            address: publicKeys[index],\n            privateKey,\n        }));\n\n        const wallet = new ethers.Wallet(this.wallets[0].privateKey, this.provider);\n        this.hubContract = new ethers.Contract(hubContractAddress, Hub, wallet);\n        this.ParametersStorageInterface = new ethers.utils.Interface(ParametersStorage);\n\n        // provider.ready resolves when the JSON-RPC port is open, which happens before Hardhat\n        // finishes deploying contracts. Poll the hub contract until it actually responds so that\n        // the step only completes once the full on-chain environment is ready.\n        await this.provider.ready;\n        await this._waitForContracts(port, _console);\n    }\n\n    async _waitForContracts(port, _console) {\n        const MAX_ATTEMPTS = 60;\n        const INTERVAL_MS = 5000;\n        for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                await this.hubContract.getContractAddress('ParametersStorage');\n                _console.log(`Contracts deployed and ready on port ${port}`);\n                return;\n            } catch {\n                _console.log(\n                    `Waiting for contracts on port ${port} (attempt ${attempt + 1}/${MAX_ATTEMPTS})…`,\n                );\n                // eslint-disable-next-line no-await-in-loop\n                await new Promise((r) => setTimeout(r, INTERVAL_MS));\n            }\n        }\n        throw new Error(\n            `Hub contract on port ${port} did not become ready after ${MAX_ATTEMPTS * (INTERVAL_MS / 1000)}s`,\n        );\n    }\n\n    async stop() {\n        const commandLog = execSync(`npm run kill:local_blockchain -- ${this.port}`);\n        console.log(`Killing hardhat process: ${commandLog.toString()}`);\n        this.startBlockchainProcess.kill();\n    }\n\n    getWallets() {\n        return this.wallets;\n    }\n\n    async setParametersStorageParams(params) {\n        const parametersStorageAddress = await this.hubContract.getContractAddress(\n            'ParametersStorage',\n        );\n        for (const parameter of Object.keys(params)) {\n            const blockchainMethodName = `set${\n                parameter.charAt(0).toUpperCase() + parameter.slice(1)\n            }`;\n            console.log(`Setting ${parameter} in parameters storage to: ${params[parameter]}`);\n            const encodedData = this.ParametersStorageInterface.encodeFunctionData(\n                blockchainMethodName,\n                [params[parameter]],\n            );\n            // eslint-disable-next-line no-await-in-loop\n            await this.hubContract.forwardCall(parametersStorageAddress, encodedData);\n        }\n    }\n\n    async setR0(r0) {\n        console.log(`Setting R0 in parameters storage to: ${r0}`);\n        const encodedData = this.ParametersStorageInterface.encodeFunctionData('setR0', [r0]);\n        const parametersStorageAddress = await this.hubContract.getContractAddress(\n            'ParametersStorage',\n        );\n        await this.hubContract.forwardCall(parametersStorageAddress, encodedData);\n    }\n\n    async setR1(r1) {\n        console.log(`Setting R1 in parameters storage to: ${r1}`);\n        const encodedData = this.ParametersStorageInterface.encodeFunctionData('setR1', [r1]);\n        const parametersStorageAddress = await this.hubContract.getContractAddress(\n            'ParametersStorage',\n        );\n        await this.hubContract.forwardCall(parametersStorageAddress, encodedData);\n    }\n\n    async setFinalizationCommitsNumber(commitsNumber) {\n        console.log(`Setting finalizationCommitsNumber in parameters storage to: ${commitsNumber}`);\n        const encodedData = this.ParametersStorageInterface.encodeFunctionData(\n            'setFinalizationCommitsNumber',\n            [commitsNumber],\n        );\n        const parametersStorageAddress = await this.hubContract.getContractAddress(\n            'ParametersStorage',\n        );\n        await this.hubContract.forwardCall(parametersStorageAddress, encodedData);\n    }\n}\n\nexport default LocalBlockchain;\n"
  },
  {
    "path": "test/bdd/steps/lib/ot-node-process.mjs",
    "content": "import { setTimeout } from 'timers/promises';\nimport OTNode from '../../../../ot-node.js';\nimport HttpApiHelper from '../../../utilities/http-api-helper.mjs';\n\nconst httpApiHelper = new HttpApiHelper();\n\n// In small BDD test networks (3 nodes), libp2p's KadDHT periodically performs\n// peer lookups that fail because the routing table is empty/sparse.  These\n// surface as unhandled promise rejections which, in Node.js >= 15, terminate\n// the process.  Catching them here keeps the test nodes alive.\nprocess.on('unhandledRejection', (reason) => {\n    const msg = reason instanceof Error ? reason.message : String(reason);\n    const code = reason?.code;\n    if (code === 'ERR_LOOKUP_FAILED' || code === 'NOT_FOUND' || code === 'NO_ROUTERS_AVAILABLE') {\n        // Expected in small test networks — suppress silently.\n        return;\n    }\n    console.error(`[test-node] Unhandled rejection: ${msg}`);\n});\n\nprocess.on('message', async (data) => {\n    const config = JSON.parse(data);\n    try {\n        process.env.OPERATIONAL_DB_NAME = config.operationalDatabase.databaseName;\n\n        // OTNode constructor reads configjson[NODE_ENV] as the default config base.\n        // We must keep NODE_ENV='test' during construction so the 'test' defaults\n        // (e.g. tripleStore.ot-blazegraph.enabled=true) are used.\n        const newNode = new OTNode(config);\n\n        // Switch to 'development' AFTER config is built but BEFORE start() so the\n        // CommandExecutor creates per-node BullMQ queues (command-executor-{nodeName})\n        // instead of a shared 'command-executor' queue that causes job stealing.\n        process.env.NODE_ENV = 'development';\n        await newNode.start();\n\n        const nodeHostname = `http://localhost:${config.rpcPort}`;\n        const MAX_HTTP_POLL_ATTEMPTS = 30;\n        let started = false;\n        for (let attempt = 0; attempt < MAX_HTTP_POLL_ATTEMPTS; attempt += 1) {\n            try {\n                // eslint-disable-next-line no-await-in-loop\n                await httpApiHelper.info(nodeHostname);\n                started = true;\n                break;\n            } catch {\n                // eslint-disable-next-line no-await-in-loop\n                await setTimeout(1000);\n            }\n        }\n        if (!started) {\n            throw new Error(\n                `Node HTTP API on port ${config.rpcPort} did not become ready after ${MAX_HTTP_POLL_ATTEMPTS}s`,\n            );\n        }\n\n        process.send({ status: 'STARTED' });\n    } catch (error) {\n        process.send({ error: error.message });\n    }\n});\n"
  },
  {
    "path": "test/modules/telemetry/config.json",
    "content": "{\n    \"modules\": {\n        \"telemetry\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"ot-telemetry\": {\n                    \"enabled\": true,\n                    \"package\": \"./telemetry/implementation/ot-telemetry.js\",\n                    \"config\": {\n                        \"sendTelemetryData\": false,\n                        \"signalingServerUrl\": \"null\"\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test/modules/telemetry/telemetry.js",
    "content": "import { readFile } from 'fs/promises';\nimport { describe, it, before } from 'mocha';\nimport { expect, assert } from 'chai';\nimport Logger from '../../../src/logger/logger.js';\nimport TelemetryModuleManager from '../../../src/modules/telemetry/telemetry-module-manager.js';\n\nlet logger;\nlet telemetryModuleManager;\nconst config = JSON.parse(await readFile('./test/modules/telemetry/config.json'));\n\ndescribe('Telemetry module', () => {\n    before('Initialize logger', () => {\n        logger = new Logger('trace');\n        logger.info = () => {};\n    });\n\n    describe('Handle received events', () => {\n        it('should call onEventReceived when event is emitted', async () => {\n            const eventEmitter = {\n                eventListeners: {},\n\n                on(eventName, callback) {\n                    if (!this.eventListeners[eventName]) {\n                        this.eventListeners[eventName] = [];\n                    }\n                    this.eventListeners[eventName].push(callback);\n                },\n\n                emit(eventName, ...args) {\n                    if (this.eventListeners[eventName]) {\n                        this.eventListeners[eventName].forEach((callback) => callback(...args));\n                    }\n                },\n            };\n\n            let callbackCalled = false;\n\n            function onEventReceived() {\n                callbackCalled = true;\n            }\n\n            telemetryModuleManager = new TelemetryModuleManager({ config, logger, eventEmitter });\n            await telemetryModuleManager.initialize();\n            telemetryModuleManager.listenOnEvents(onEventReceived);\n\n            eventEmitter.emit('operation_status_changed');\n\n            assert(expect(callbackCalled).to.be.true);\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/api/http-api-router.test.js",
    "content": "import { beforeEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport sinon from 'sinon';\nimport HttpApiRouter from '../../../src/controllers/http-api/http-api-router.js';\nimport JsonSchemaServiceMock from '../mock/json-schema-service-mock.js';\nimport HttpClientModuleManagerMock from '../mock/http-client-module-manager-mock.js';\nimport { HTTP_API_ROUTES } from '../../../src/constants/constants.js';\n\ndescribe('HTTP API Router test', async () => {\n    let httpApiRouter;\n    const controllerMocks = {};\n\n    beforeEach(() => {\n        // Mock Controllers\n        Object.keys(HTTP_API_ROUTES).forEach((version) => {\n            Object.keys(HTTP_API_ROUTES[version]).forEach((operation) => {\n                const versionedController = `${operation}HttpApiController${\n                    version.charAt(1).toUpperCase() + version.slice(2)\n                }`;\n                controllerMocks[versionedController] = { handleRequest: sinon.stub() };\n            });\n        });\n\n        // Mock context\n        const ctx = {\n            httpClientModuleManager: new HttpClientModuleManagerMock(),\n            jsonSchemaService: new JsonSchemaServiceMock(),\n            ...controllerMocks,\n        };\n\n        // Initialize HttpApiRouter with mocks\n        httpApiRouter = new HttpApiRouter(ctx);\n    });\n\n    it('Router has all defined routes', async () => {\n        // Extract unique HTTP methods present across all versions\n        const httpMethods = new Set();\n        Object.values(HTTP_API_ROUTES).forEach((routes) => {\n            Object.values(routes).forEach((route) => {\n                httpMethods.add(route.method);\n            });\n        });\n\n        // Create spies for each extracted HTTP method on each router instance and httpClientModuleManager\n        const spies = {};\n        Object.keys(HTTP_API_ROUTES).forEach((version) => {\n            spies[version] = {};\n            Array.from(httpMethods).forEach((method) => {\n                spies[version][method] = sinon.spy(httpApiRouter.routers[version], method);\n            });\n        });\n        const httpClientModuleManagerUseSpy = sinon.spy(\n            httpApiRouter.httpClientModuleManager,\n            'use',\n        );\n\n        // Initialize the routes\n        await httpApiRouter.initialize();\n\n        // Validate each route\n        Object.entries(HTTP_API_ROUTES).forEach(([version, routes]) => {\n            expect(httpClientModuleManagerUseSpy.calledWith(`/${version}`)).to.equal(true);\n\n            Object.values(routes).forEach((routeDetails) => {\n                const { method, path } = routeDetails;\n                expect(spies[version][method].calledWith(path)).to.equal(true);\n            });\n        });\n        expect(httpClientModuleManagerUseSpy.calledWith('/latest')).to.equal(true);\n        expect(httpClientModuleManagerUseSpy.calledWith('/')).to.equal(true);\n\n        // Restore all spies\n        Object.values(spies).forEach((versionSpies) => {\n            Object.values(versionSpies).forEach((spy) => {\n                spy.restore();\n            });\n        });\n        httpClientModuleManagerUseSpy.restore();\n    });\n});\n"
  },
  {
    "path": "test/unit/commands/operation-id-cleaner-command.test.js",
    "content": "import { describe, it, beforeEach, afterEach } from 'mocha';\nimport { expect } from 'chai';\nimport sinon from 'sinon';\n\nimport OperationIdCleanerCommand from '../../../src/commands/cleaners/operation-id-cleaner-command.js';\nimport {\n    OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n    OPERATION_ID_FILES_FOR_REMOVAL_MAX_NUMBER,\n    OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS,\n    OPERATION_ID_STATUS,\n} from '../../../src/constants/constants.js';\n\ndescribe('OperationIdCleanerCommand', () => {\n    let clock;\n    let operationIdService;\n    let repositoryModuleManager;\n    let logger;\n    let command;\n\n    beforeEach(() => {\n        clock = sinon.useFakeTimers(new Date('2023-01-01T00:00:00Z').getTime());\n\n        operationIdService = {\n            getOperationIdMemoryCacheSizeBytes: sinon.stub().returns(1024),\n            getOperationIdFileCacheSizeBytes: sinon.stub().resolves(2048),\n            removeExpiredOperationIdMemoryCache: sinon.stub().resolves(512),\n            removeExpiredOperationIdFileCache: sinon.stub().resolves(3),\n        };\n\n        repositoryModuleManager = {\n            removeOperationIdRecord: sinon.stub().resolves(),\n        };\n\n        logger = {\n            debug: sinon.spy(),\n            info: sinon.spy(),\n            warn: sinon.spy(),\n            error: sinon.spy(),\n        };\n\n        command = new OperationIdCleanerCommand({\n            logger,\n            repositoryModuleManager,\n            operationIdService,\n            fileService: {},\n        });\n    });\n\n    afterEach(() => {\n        clock.restore();\n    });\n\n    it('cleans memory with 1h TTL and files with 24h TTL while reporting footprint', async () => {\n        await command.execute();\n\n        expect(operationIdService.getOperationIdMemoryCacheSizeBytes.calledOnce).to.be.true;\n        expect(operationIdService.getOperationIdFileCacheSizeBytes.calledOnce).to.be.true;\n\n        expect(\n            repositoryModuleManager.removeOperationIdRecord.calledWith(\n                Date.now() - OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n                [OPERATION_ID_STATUS.COMPLETED, OPERATION_ID_STATUS.FAILED],\n            ),\n        ).to.be.true;\n\n        expect(\n            operationIdService.removeExpiredOperationIdMemoryCache.calledWith(\n                OPERATION_ID_MEMORY_CLEANUP_TIME_MILLS,\n            ),\n        ).to.be.true;\n\n        expect(\n            operationIdService.removeExpiredOperationIdFileCache.calledWith(\n                OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n                OPERATION_ID_FILES_FOR_REMOVAL_MAX_NUMBER,\n            ),\n        ).to.be.true;\n\n        expect(logger.debug.called).to.be.true;\n    });\n\n    it('handles missing memory cache gracefully', async () => {\n        operationIdService.getOperationIdMemoryCacheSizeBytes.throws(new Error('no memory cache'));\n        await command.execute();\n\n        expect(\n            repositoryModuleManager.removeOperationIdRecord.calledWith(\n                Date.now() - OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n                [OPERATION_ID_STATUS.COMPLETED, OPERATION_ID_STATUS.FAILED],\n            ),\n        ).to.be.true;\n\n        expect(\n            operationIdService.removeExpiredOperationIdFileCache.calledWith(\n                OPERATION_ID_COMMAND_CLEANUP_TIME_MILLS,\n                OPERATION_ID_FILES_FOR_REMOVAL_MAX_NUMBER,\n            ),\n        ).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/controllers/publish-http-api-controller-v1.test.js",
    "content": "import { describe, it } from 'mocha';\nimport { expect } from 'chai';\n\nimport PublishController from '../../../src/controllers/http-api/v1/publish-http-api-controller-v1.js';\nimport { PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS } from '../../../src/constants/constants.js';\n\nconst createRes = () => {\n    const res = {\n        statusCode: null,\n        body: null,\n        status(code) {\n            this.statusCode = code;\n            return this;\n        },\n        json(payload) {\n            this.body = payload;\n            return this;\n        },\n        send(payload) {\n            this.body = payload;\n            return this;\n        },\n    };\n    return res;\n};\n\ndescribe('publish-http-api-controller-v1', () => {\n    const baseCtx = () => {\n        const addedCommands = [];\n        return {\n            commandExecutor: {\n                add: async (cmd) => {\n                    addedCommands.push(cmd);\n                },\n                _added: addedCommands,\n            },\n            publishService: {\n                getOperationName: () => 'publish',\n            },\n            operationIdService: {\n                generateOperationId: async () => 'op-id-123',\n                emitChangeEvent: () => {},\n                updateOperationIdStatus: async () => {},\n                cacheOperationIdDataToMemory: async () => {},\n                cacheOperationIdDataToFile: async () => {},\n            },\n            repositoryModuleManager: {\n                createOperationRecord: async () => {},\n            },\n            pendingStorageService: {\n                cacheDataset: async () => {},\n            },\n            networkModuleManager: {\n                getPeerId: () => ({ toB58String: () => 'peer-self' }),\n            },\n            blockchainModuleManager: {\n                getMinimumRequiredSignatures: async () => PUBLISH_MIN_NUM_OF_NODE_REPLICATIONS,\n            },\n            logger: {\n                info: () => {},\n                warn: () => {},\n                error: () => {},\n            },\n        };\n    };\n\n    it('clamps minimumNumberOfNodeReplications to on-chain minimum', async () => {\n        const ctx = baseCtx();\n        ctx.blockchainModuleManager.getMinimumRequiredSignatures = async () => 5; // on-chain min\n        const controller = new PublishController(ctx);\n\n        const req = {\n            body: {\n                dataset: { public: {} },\n                datasetRoot: '0xroot',\n                blockchain: 'hardhat',\n                minimumNumberOfNodeReplications: 2, // below chain min\n            },\n        };\n        const res = createRes();\n\n        await controller.handleRequest(req, res);\n\n        expect(res.statusCode).to.equal(202);\n        const added = ctx.commandExecutor._added[0];\n        expect(added.data.minimumNumberOfNodeReplications).to.equal(5);\n    });\n\n    it('allows higher user override than on-chain minimum', async () => {\n        const ctx = baseCtx();\n        ctx.blockchainModuleManager.getMinimumRequiredSignatures = async () => 3; // on-chain min\n        const controller = new PublishController(ctx);\n\n        const req = {\n            body: {\n                dataset: { public: {} },\n                datasetRoot: '0xroot',\n                blockchain: 'hardhat',\n                minimumNumberOfNodeReplications: 7, // above chain min\n            },\n        };\n        const res = createRes();\n\n        await controller.handleRequest(req, res);\n\n        expect(res.statusCode).to.equal(202);\n        const added = ctx.commandExecutor._added[0];\n        expect(added.data.minimumNumberOfNodeReplications).to.equal(7);\n    });\n\n    it('falls back to on-chain minimum when user value is zero or invalid', async () => {\n        const ctx = baseCtx();\n        ctx.blockchainModuleManager.getMinimumRequiredSignatures = async () => 4; // on-chain min\n        const controller = new PublishController(ctx);\n\n        const req = {\n            body: {\n                dataset: { public: {} },\n                datasetRoot: '0xroot',\n                blockchain: 'hardhat',\n                minimumNumberOfNodeReplications: 0, // invalid/zero\n            },\n        };\n        const res = createRes();\n\n        await controller.handleRequest(req, res);\n\n        expect(res.statusCode).to.equal(202);\n        const added = ctx.commandExecutor._added[0];\n        expect(added.data.minimumNumberOfNodeReplications).to.equal(4);\n    });\n});\n"
  },
  {
    "path": "test/unit/middleware/authentication-middleware.test.js",
    "content": "import sinon from 'sinon';\nimport { describe, it, afterEach } from 'mocha';\nimport { expect } from 'chai';\n\nimport authenticationMiddleware from '../../../src/modules/http-client/implementation/middleware/authentication-middleware.js';\nimport AuthService from '../../../src/service/auth-service.js';\n\ndescribe('authentication middleware test', async () => {\n    const sandbox = sinon.createSandbox();\n\n    const getAuthService = (options) =>\n        sandbox.createStubInstance(AuthService, {\n            authenticate: options.isAuthenticated,\n            isPublicOperation: options.isPublicOperation,\n        });\n\n    afterEach(() => {\n        sandbox.restore();\n    });\n\n    it('calls next if isPublic evaluated to true', async () => {\n        const middleware = authenticationMiddleware(\n            getAuthService({\n                isPublicOperation: true,\n            }),\n        );\n\n        const req = { headers: { authorization: 'Bearer token' }, path: '/publish' };\n\n        const spySend = sandbox.spy();\n        const spyStatus = sandbox.spy(() => ({ send: spySend }));\n\n        const nextSpy = sandbox.spy();\n        await middleware(req, { status: spyStatus }, nextSpy);\n\n        expect(nextSpy.calledOnce).to.be.true;\n        expect(spyStatus.notCalled).to.be.true;\n        expect(spySend.notCalled).to.be.true;\n    });\n\n    it('calls next if isAuthenticated is evaluated as true', async () => {\n        const middleware = authenticationMiddleware(\n            getAuthService({\n                isPublicOperation: false,\n                isAuthenticated: true,\n            }),\n        );\n\n        const req = { headers: { authorization: 'Bearer token' }, path: '/publish' };\n\n        const spySend = sandbox.spy();\n        const spyStatus = sandbox.spy(() => ({ send: spySend }));\n\n        const nextSpy = sandbox.spy();\n        await middleware(req, { status: spyStatus }, nextSpy);\n\n        expect(nextSpy.calledOnce).to.be.true;\n        expect(spyStatus.notCalled).to.be.true;\n        expect(spySend.notCalled).to.be.true;\n    });\n\n    it('returns 401 if isAuthenticated is evaluated as false', async () => {\n        const middleware = authenticationMiddleware(\n            getAuthService({\n                isPublicOperation: false,\n                isAuthenticated: false,\n            }),\n        );\n\n        const req = { headers: { authorization: 'Bearer token' }, path: '/publish' };\n\n        const spySend = sandbox.spy();\n        const spyStatus = sandbox.spy(() => ({ send: spySend }));\n        const spyNext = sandbox.spy();\n\n        await middleware(req, { status: spyStatus }, spyNext);\n\n        const [statusCode] = spyStatus.args[0];\n\n        expect(statusCode).to.be.eq(401);\n        expect(spyStatus.calledOnce).to.be.true;\n        expect(spySend.calledOnce).to.be.true;\n        expect(spyNext.notCalled).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/middleware/authorization-middleware.test.js",
    "content": "import sinon from 'sinon';\nimport { describe, it, afterEach } from 'mocha';\nimport { expect } from 'chai';\n\nimport authorizationMiddleware from '../../../src/modules/http-client/implementation/middleware/authorization-middleware.js';\nimport AuthService from '../../../src/service/auth-service.js';\n\ndescribe('authentication middleware test', async () => {\n    const sandbox = sinon.createSandbox();\n\n    const getAuthService = (options) =>\n        sandbox.createStubInstance(AuthService, {\n            authenticate: options.isAuthenticated,\n            isAuthorized: options.isAuthorized,\n            isPublicOperation: options.isPublicOperation,\n        });\n\n    afterEach(() => {\n        sandbox.restore();\n    });\n\n    it('calls next if isPublicOperation is resolved to true', async () => {\n        const middleware = authorizationMiddleware(\n            getAuthService({\n                isPublicOperation: true,\n            }),\n        );\n\n        const req = { headers: { authorization: 'Bearer token' }, path: '/publish' };\n\n        const spySend = sandbox.spy();\n        const spyStatus = sandbox.spy(() => ({ send: spySend }));\n\n        const nextSpy = sandbox.spy();\n        await middleware(req, { status: spyStatus }, nextSpy);\n\n        expect(nextSpy.calledOnce).to.be.true;\n        expect(spyStatus.notCalled).to.be.true;\n        expect(spySend.notCalled).to.be.true;\n    });\n\n    it('calls next if isAuthenticated is evaluated as true', async () => {\n        const middleware = authorizationMiddleware(\n            getAuthService({\n                isPublicOperation: true,\n            }),\n        );\n\n        const req = { headers: { authorization: 'Bearer token' }, path: '/publish' };\n\n        const spySend = sandbox.spy();\n        const spyStatus = sandbox.spy(() => ({ send: spySend }));\n\n        const nextSpy = sandbox.spy();\n        await middleware(req, { status: spyStatus }, nextSpy);\n\n        expect(nextSpy.calledOnce).to.be.true;\n        expect(spyStatus.notCalled).to.be.true;\n        expect(spySend.notCalled).to.be.true;\n    });\n\n    it('returns 403 if isAuthenticated is evaluated as false', async () => {\n        const middleware = authorizationMiddleware(\n            getAuthService({\n                isPublicOperation: false,\n                isAuthenticated: false,\n            }),\n        );\n\n        const req = { headers: { authorization: 'Bearer token' }, path: '/publish' };\n\n        const spySend = sandbox.spy();\n        const spyStatus = sandbox.spy(() => ({ send: spySend }));\n        const spyNext = sandbox.spy();\n\n        await middleware(req, { status: spyStatus }, spyNext);\n\n        const [statusCode] = spyStatus.args[0];\n\n        expect(statusCode).to.be.eq(403);\n        expect(spyStatus.calledOnce).to.be.true;\n        expect(spySend.calledOnce).to.be.true;\n        expect(spyNext.notCalled).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/mock/blockchain-module-manager-mock.js",
    "content": "import { ethers } from 'ethers';\n\nclass BlockchainModuleManagerMock {\n    getR2() {\n        return 20;\n    }\n\n    getR1() {\n        return 8;\n    }\n\n    getR0() {\n        return 3;\n    }\n\n    encodePacked(blockchain, types, values) {\n        return ethers.utils.solidityPack(types, values);\n    }\n\n    convertBytesToUint8Array(blockchain, bytesLikeData) {\n        return ethers.utils.arrayify(bytesLikeData);\n    }\n\n    convertToWei(blockchainId, value) {\n        return ethers.utils.parseUnits(value.toString(), 'ether');\n    }\n\n    toBigNumber(blockchain, value) {\n        return ethers.BigNumber.from(value);\n    }\n\n    getAssertionSize(blockchain, assertionId) {\n        return 246;\n    }\n\n    getAssertionTriplesNumber(blockchain, assertionId) {\n        return undefined;\n    }\n\n    getAssertionChunksNumber(blockchain, assertionId) {\n        return undefined;\n    }\n}\n\nexport default BlockchainModuleManagerMock;\n"
  },
  {
    "path": "test/unit/mock/command-executor-mock.js",
    "content": "class CommandExecutorMock {\n    add(addCommand) {}\n}\n\nexport default CommandExecutorMock;\n"
  },
  {
    "path": "test/unit/mock/event-emitter-mock.js",
    "content": "class EventEmitterMock {}\n\nexport default EventEmitterMock;\n"
  },
  {
    "path": "test/unit/mock/http-client-module-manager-mock.js",
    "content": "import express from 'express';\n\nclass HttpClientModuleManagerMock {\n    createRouterInstance() {\n        return express.Router();\n    }\n\n    initializeBeforeMiddlewares() {}\n\n    async listen() {}\n\n    use(path, callback) {}\n\n    selectMiddlewares(options) {\n        return [];\n    }\n\n    initializeAfterMiddlewares() {}\n}\n\nexport default HttpClientModuleManagerMock;\n"
  },
  {
    "path": "test/unit/mock/json-schema-service-mock.js",
    "content": "class JsonSchemaServiceMock {}\n\nexport default JsonSchemaServiceMock;\n"
  },
  {
    "path": "test/unit/mock/network-module-manager-mock.js",
    "content": "class NetworkModuleManagerMock {\n    getPeerId() {\n        return {\n            toB58String: () => 'myPeerId',\n        };\n    }\n}\n\nexport default NetworkModuleManagerMock;\n"
  },
  {
    "path": "test/unit/mock/operation-id-service-mock.js",
    "content": "class OperationIdServiceMock {\n    constructor(ctx) {\n        this.repositoryModuleManager = ctx.repositoryModuleManager;\n    }\n\n    cacheOperationIdDataToFile(operationId, data) {}\n\n    cacheOperationIdDataToMemory(operationId, data) {}\n\n    async updateOperationIdStatus(\n        operationId,\n        blockchain,\n        status,\n        errorMessage = null,\n        errorType = null,\n    ) {\n        await this.repositoryModuleManager.updateOperationIdRecord(\n            {\n                status,\n                timestamp: new Date().toISOString(),\n            },\n            operationId,\n        );\n    }\n}\n\nexport default OperationIdServiceMock;\n"
  },
  {
    "path": "test/unit/mock/repository-module-manager-mock.js",
    "content": "class RepositoryModuleManagerMock {\n    responseStatuses = [\n        {\n            id: 1,\n            operationId: 'f6354c2c-d460-11ed-afa1-0242ac120002',\n            keyword: 'origintrail',\n            status: 'COMPLETED',\n            message: 'message',\n            createdAt: '1970-01-01 00:00:00',\n            updatedAt: '1970-01-01 00:00:00',\n        },\n    ];\n\n    getAllPeerRecords() {\n        return [\n            {\n                peerId: 'QmcJY13uLyt2VQ6QiVNcYiWaxdfaHWHj3T7G472uaHPBf7',\n                blockchainId: 'ganache',\n                ask: '0.2824612246520951',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0x6e08776479a010d563855dbc371a66f692d3edcbcf2b02c30f9879ebe02244e8',\n            },\n            {\n                peerId: 'Qmcxo88zf5zEvyBLYTrtfG8nGJQW6zHpf58b5MUcjoYVqL',\n                blockchainId: 'ganache',\n                ask: '0.11680988694381877',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0x113d3da32b0e0b7031d188736792bbea0baf7911acb905511ac7dda2be9a6f55',\n            },\n            {\n                peerId: 'QmQeNwBzgeMQxquQEDXvBHqXBHNBEvKHtyHURg4QvnoLrD',\n                blockchainId: 'ganache',\n                ask: '0.25255488168658036',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0xba14ac66ab5be40bf458bad9b4e9f10a9d06375b233e91a6ce3c2d4cbf9deea5',\n            },\n            {\n                peerId: 'QmU4ty8X8L4Xk6cbDCoyJUhgeBNLDo3HprTGEhNd9CtiT7',\n                blockchainId: 'ganache',\n                ask: '0.25263875217271087',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0x5b3fdb88b3270a99cc89d28e0a4504d28789e5f8ca53080aa7608db48546d56b',\n            },\n            {\n                peerId: 'QmWmgmMCQQ1awraTeQqwsbWgqtR3ZMuX7NhbHyiftuAspb',\n                blockchainId: 'ganache',\n                ask: '0.2429885059428509',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0x820a8e38cb792b89c8b69eb9c192faf3def6175c97c4c0f17708161bcb9c5028',\n            },\n            {\n                peerId: 'QmWyf3dtqJnhuCpzEDTNmNFYc5tjxTrXhGcUUmGHdg2gtj',\n                blockchainId: 'ganache',\n                ask: '0.210617584797714',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0xf764186e9b675f3fd00af72026cf075d05ce8fc951ba089351d645b363acd3d3',\n            },\n            {\n                peerId: 'QmXgeHgBVbd7iyTp8PapUAyeKciqbsXTEvsakCjW7wZRqT',\n                blockchainId: 'ganache',\n                ask: '0.2290449496761527',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0xaaeed7b766483aef7cf2d07325f336b3e703e2b7573e540ca8c6e2aab34265c3',\n            },\n            {\n                peerId: 'QmYys42KLmGEE9hEmJCVCe3SR3G9zf4epoAwDUK7pVUP6S',\n                blockchainId: 'ganache',\n                ask: '0.1637075464317365',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0xc3bb7b5433ebe62ff9e98c6d439223d07d44e16e7d5e210e727823f87c0ef24b',\n            },\n            {\n                peerId: 'QmZi2nDhZJfa1Z5iXjvxQ1BigpR8TdTQ3gWQDGecn34e9x',\n                blockchainId: 'ganache',\n                ask: '0.10242295311162795',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0x510ca60cdd7b33bf8d978576981ae7f9caaf5f133ddd40693d8ce007614c0a09',\n            },\n            {\n                peerId: 'QmZueq5jip24v5dbCSBGt8v16hPjUN1CXRb3zGaxH1jfHM',\n                blockchainId: 'ganache',\n                ask: '0.23374911902136858',\n                stake: '50000.0',\n                lastSeen: '1970-01-01 00:00:00',\n                lastDialed: '1970-01-01 00:00:00',\n                sha256: '0x7b4f717bd647104a72c7f1fce4600366982f36ebb1cef41540a5541c8e8ca1dd',\n            },\n        ];\n    }\n\n    getAllResponseStatuses() {\n        return this.responseStatuses;\n    }\n\n    async getOperationResponsesStatuses(operation, operationId) {\n        return this.responseStatuses.filter((rs) => rs.operationId === operationId);\n    }\n\n    async updateOperationIdRecord(data, operationId) {\n        this.responseStatuses = this.responseStatuses.map((rs) =>\n            rs.operationId === operationId\n                ? { ...rs, status: data.status, updatedAt: data.timestamp }\n                : rs,\n        );\n    }\n\n    async updateOperationStatus(operation, operationId, status) {\n        this.responseStatuses = this.responseStatuses.map((rs) =>\n            rs.operationId === operationId\n                ? { ...rs, status, updatedAt: new Date().toISOString() }\n                : rs,\n        );\n    }\n\n    async createOperationResponseRecord(status, operation, operationId, errorMessage) {\n        this.responseStatuses = [\n            ...this.responseStatuses,\n            {\n                id: this.responseStatuses[this.responseStatuses.length - 1].id + 1,\n                status,\n                operationId,\n                createdAt: new Date().toISOString(),\n                updatedAt: new Date().toISOString(),\n            },\n        ];\n    }\n}\n\nexport default RepositoryModuleManagerMock;\n"
  },
  {
    "path": "test/unit/mock/validation-module-manager-mock.js",
    "content": "import { ethers } from 'ethers';\n\nclass ValidationModuleManagerMock {\n    callHashFunction(data) {\n        const bytesLikeData = ethers.utils.toUtf8Bytes(data);\n        return ethers.utils.sha256(bytesLikeData);\n    }\n\n    getHashFunctionName() {\n        return 'sha256';\n    }\n\n    calculateRoot(assertion) {\n        return '0xde58cc52a5ce3a04ae7a05a13176226447ac02489252e4d37a72cbe0aea46b42';\n    }\n}\n\nexport default ValidationModuleManagerMock;\n"
  },
  {
    "path": "test/unit/modules/repository/config.json",
    "content": "{\n    \"modules\": {\n        \"repository\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"sequelize-repository\": {\n                    \"enabled\": true,\n                    \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                    \"config\": {\n                        \"database\": \"operationaldb-test\",\n                        \"user\": \"root\",\n                        \"password\": \"\",\n                        \"port\": \"3306\",\n                        \"host\": \"localhost\",\n                        \"dialect\": \"mysql\",\n                        \"logging\": false\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test/unit/modules/repository/repository.test.js",
    "content": "import { utils } from 'ethers';\nimport { describe, it, before, beforeEach, afterEach, after } from 'mocha';\nimport { expect, assert } from 'chai';\nimport { readFile } from 'fs/promises';\nimport Logger from '../../../../src/logger/logger.js';\nimport RepositoryModuleManager from '../../../../src/modules/repository/repository-module-manager.js';\n\nlet logger;\nlet repositoryModuleManager;\nconst config = JSON.parse(await readFile('./test/unit/modules/repository/config.json'));\n\nconst blockchain = 'hardhat';\nconst createAgreement = ({\n    blockchainId = blockchain,\n    assetStorageContractAddress = '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07',\n    tokenId,\n    id = null,\n    startTime,\n    epochsNumber = 2,\n    epochLength = 100,\n    scoreFunctionId = 1,\n    proofWindowOffsetPerc = 66,\n    hashFunctionId = 1,\n    keyword = '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca0768e44dc71bf509adfccbea9df949f253afa56796a3a926203f90a1e4914247d3',\n    assertionId = '0x68e44dc71bf509adfccbea9df949f253afa56796a3a926203f90a1e4914247d3',\n    stateIndex = 1,\n    lastCommitEpoch = null,\n    lastProofEpoch = null,\n}) => {\n    const agreementId =\n        id ??\n        utils.sha256(\n            utils.toUtf8Bytes(\n                utils.solidityPack(\n                    ['address', 'uint256', 'bytes'],\n                    [assetStorageContractAddress, tokenId, keyword],\n                ),\n            ),\n        );\n    return {\n        blockchainId,\n        assetStorageContractAddress,\n        tokenId,\n        agreementId,\n        startTime,\n        epochsNumber,\n        epochLength,\n        scoreFunctionId,\n        proofWindowOffsetPerc,\n        hashFunctionId,\n        keyword,\n        assertionId,\n        stateIndex,\n        lastCommitEpoch,\n        lastProofEpoch,\n    };\n};\n\ndescribe('Repository module', () => {\n    before('Initialize repository module manager', async function initializeRepository() {\n        this.timeout(30_000);\n        logger = new Logger('trace');\n        logger.info = () => {};\n        repositoryModuleManager = new RepositoryModuleManager({ config, logger });\n        await repositoryModuleManager.initialize();\n        await repositoryModuleManager.destroyAllRecords('service_agreement');\n    });\n\n    afterEach('Destroy all records', async function destroyAllRecords() {\n        this.timeout(30_000);\n        await repositoryModuleManager.destroyAllRecords('service_agreement');\n    });\n\n    after(async function dropDatabase() {\n        this.timeout(30_000);\n        await repositoryModuleManager.dropDatabase();\n    });\n\n    describe('Empty repository', () => {\n        it('returns empty list if no service agreements', async () => {\n            const eligibleAgreements =\n                await repositoryModuleManager.getEligibleAgreementsForSubmitCommit(\n                    Date.now(),\n                    blockchain,\n                    25,\n                );\n\n            assert(expect(eligibleAgreements).to.exist);\n            expect(eligibleAgreements).to.be.instanceOf(Array);\n            expect(eligibleAgreements).to.have.length(0);\n        });\n    });\n    describe('Insert and update service agreement', () => {\n        const agreement = {\n            blockchainId: blockchain,\n            assetStorageContractAddress: '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca07',\n            tokenId: 0,\n            agreementId: '0x44cf660357e2d7462c25fd8e50b68abe332d7a70b07a76e92f628846ea585881',\n            startTime: 1683032289,\n            epochsNumber: 2,\n            epochLength: 360,\n            scoreFunctionId: 1,\n            proofWindowOffsetPerc: 66,\n            hashFunctionId: 1,\n            keyword:\n                '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca0768e44dc71bf509adfccbea9df949f253afa56796a3a926203f90a1e4914247d3',\n            assertionId: '0x68e44dc71bf509adfccbea9df949f253afa56796a3a926203f90a1e4914247d3',\n            stateIndex: 1,\n        };\n\n        it('inserts service agreement', async () => {\n            const inserted = await repositoryModuleManager.updateServiceAgreementRecord(\n                agreement.blockchainId,\n                agreement.assetStorageContractAddress,\n                agreement.tokenId,\n                agreement.agreementId,\n                agreement.startTime,\n                agreement.epochsNumber,\n                agreement.epochLength,\n                agreement.scoreFunctionId,\n                agreement.proofWindowOffsetPerc,\n                agreement.hashFunctionId,\n                agreement.keyword,\n                agreement.assertionId,\n                agreement.stateIndex,\n                agreement.lastCommitEpoch,\n                agreement.lastProofEpoch,\n            );\n            const row = inserted[0]?.dataValues;\n\n            assert(expect(row).to.exist);\n            expect(row.blockchainId).to.equal(agreement.blockchainId);\n            expect(row.assetStorageContractAddress).to.equal(agreement.assetStorageContractAddress);\n            expect(row.tokenId).to.equal(agreement.tokenId);\n            expect(row.agreementId).to.equal(agreement.agreementId);\n            expect(row.startTime).to.equal(agreement.startTime);\n            expect(row.epochsNumber).to.equal(agreement.epochsNumber);\n            expect(row.epochLength).to.equal(agreement.epochLength);\n            expect(row.scoreFunctionId).to.equal(agreement.scoreFunctionId);\n            expect(row.proofWindowOffsetPerc).to.equal(agreement.proofWindowOffsetPerc);\n            expect(row.hashFunctionId).to.equal(agreement.hashFunctionId);\n            expect(row.keyword).to.equal(agreement.keyword);\n            expect(row.assertionId).to.equal(agreement.assertionId);\n            expect(row.stateIndex).to.equal(agreement.stateIndex);\n            assert(expect(row.lastCommitEpoch).to.not.exist);\n            assert(expect(row.lastProofEpoch).to.not.exist);\n        });\n    });\n\n    describe('Eligible service agreements', () => {\n        const agreements = [\n            createAgreement({ tokenId: 0, startTime: 0 }),\n            createAgreement({\n                tokenId: 1,\n                startTime: 15,\n                lastCommitEpoch: 0,\n            }),\n            createAgreement({ tokenId: 2, startTime: 25 }),\n            createAgreement({\n                tokenId: 3,\n                startTime: 25,\n                lastCommitEpoch: 0,\n                lastProofEpoch: 0,\n            }),\n            createAgreement({ tokenId: 4, startTime: 49 }),\n        ];\n\n        beforeEach(async () => {\n            await Promise.all(\n                agreements.map((agreement) =>\n                    repositoryModuleManager.updateServiceAgreementRecord(\n                        agreement.blockchainId,\n                        agreement.assetStorageContractAddress,\n                        agreement.tokenId,\n                        agreement.agreementId,\n                        agreement.startTime,\n                        agreement.epochsNumber,\n                        agreement.epochLength,\n                        agreement.scoreFunctionId,\n                        agreement.proofWindowOffsetPerc,\n                        agreement.hashFunctionId,\n                        agreement.keyword,\n                        agreement.assertionId,\n                        agreement.stateIndex,\n                        agreement.lastCommitEpoch,\n                        agreement.lastProofEpoch,\n                    ),\n                ),\n            );\n        });\n\n        describe('getEligibleAgreementsForSubmitCommit returns correct agreements', () => {\n            const testEligibleAgreementsForSubmitCommit =\n                (currentTimestamp, commitWindowDurationPerc, expectedAgreements) => async () => {\n                    const eligibleAgreements =\n                        await repositoryModuleManager.getEligibleAgreementsForSubmitCommit(\n                            currentTimestamp,\n                            blockchain,\n                            commitWindowDurationPerc,\n                        );\n\n                    assert(expect(eligibleAgreements).to.exist);\n                    expect(eligibleAgreements).to.be.instanceOf(Array);\n                    expect(eligibleAgreements).to.have.length(expectedAgreements.length);\n                    expect(eligibleAgreements).to.have.deep.members(expectedAgreements);\n\n                    // ensure order is correct\n                    for (let i = 0; i < eligibleAgreements.length; i += 1) {\n                        assert.strictEqual(\n                            eligibleAgreements[i].timeLeftInSubmitCommitWindow,\n                            expectedAgreements[i].timeLeftInSubmitCommitWindow,\n                        );\n                    }\n                };\n\n            it(\n                'returns two eligible service agreements at timestamp 49',\n                testEligibleAgreementsForSubmitCommit(49, 25, [\n                    { ...agreements[2], currentEpoch: 0, timeLeftInSubmitCommitWindow: 1 },\n                    { ...agreements[4], currentEpoch: 0, timeLeftInSubmitCommitWindow: 25 },\n                ]),\n            );\n            it(\n                'returns one eligible service agreement at timestamp 51',\n                testEligibleAgreementsForSubmitCommit(51, 25, [\n                    { ...agreements[4], currentEpoch: 0, timeLeftInSubmitCommitWindow: 23 },\n                ]),\n            );\n            it(\n                'returns no eligible service agreement at timestamp 74',\n                testEligibleAgreementsForSubmitCommit(74, 25, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 75',\n                testEligibleAgreementsForSubmitCommit(75, 25, []),\n            );\n            it(\n                'returns one eligible service agreements at timestamp 100',\n                testEligibleAgreementsForSubmitCommit(100, 25, [\n                    { ...agreements[0], currentEpoch: 1, timeLeftInSubmitCommitWindow: 25 },\n                ]),\n            );\n            it(\n                'returns two eligible service agreements at timestamp 124',\n                testEligibleAgreementsForSubmitCommit(124, 25, [\n                    { ...agreements[0], currentEpoch: 1, timeLeftInSubmitCommitWindow: 1 },\n                    { ...agreements[1], currentEpoch: 1, timeLeftInSubmitCommitWindow: 16 },\n                ]),\n            );\n            it(\n                'returns three eligible service agreements at timestamp 125',\n                testEligibleAgreementsForSubmitCommit(125, 25, [\n                    { ...agreements[1], currentEpoch: 1, timeLeftInSubmitCommitWindow: 15 },\n                    { ...agreements[2], currentEpoch: 1, timeLeftInSubmitCommitWindow: 25 },\n                    { ...agreements[3], currentEpoch: 1, timeLeftInSubmitCommitWindow: 25 },\n                ]),\n            );\n            it(\n                'returns three eligible service agreements at timestamp 126',\n                testEligibleAgreementsForSubmitCommit(126, 25, [\n                    { ...agreements[1], currentEpoch: 1, timeLeftInSubmitCommitWindow: 14 },\n                    { ...agreements[2], currentEpoch: 1, timeLeftInSubmitCommitWindow: 24 },\n                    { ...agreements[3], currentEpoch: 1, timeLeftInSubmitCommitWindow: 24 },\n                ]),\n            );\n            it(\n                'returns three eligible service agreements at timestamp 149',\n                testEligibleAgreementsForSubmitCommit(149, 25, [\n                    { ...agreements[2], currentEpoch: 1, timeLeftInSubmitCommitWindow: 1 },\n                    { ...agreements[3], currentEpoch: 1, timeLeftInSubmitCommitWindow: 1 },\n                    { ...agreements[4], currentEpoch: 1, timeLeftInSubmitCommitWindow: 25 },\n                ]),\n            );\n            it(\n                'returns one eligible service agreements at timestamp 151',\n                testEligibleAgreementsForSubmitCommit(151, 25, [\n                    { ...agreements[4], currentEpoch: 1, timeLeftInSubmitCommitWindow: 23 },\n                ]),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 175',\n                testEligibleAgreementsForSubmitCommit(175, 25, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 225',\n                testEligibleAgreementsForSubmitCommit(225, 25, []),\n            );\n        });\n\n        describe('getEligibleAgreementsForSubmitProof returns correct agreements', () => {\n            const testEligibleAgreementsForSubmitProof =\n                (currentTimestamp, proofWindowDurationPerc, expectedAgreements) => async () => {\n                    const eligibleAgreements =\n                        await repositoryModuleManager.getEligibleAgreementsForSubmitProof(\n                            currentTimestamp,\n                            blockchain,\n                            proofWindowDurationPerc,\n                        );\n\n                    assert(expect(eligibleAgreements).to.exist);\n                    expect(eligibleAgreements).to.be.instanceOf(Array);\n                    expect(eligibleAgreements).to.have.length(expectedAgreements.length);\n                    expect(eligibleAgreements).to.have.deep.members(expectedAgreements);\n\n                    // ensure order is correct\n                    for (let i = 0; i < eligibleAgreements.length; i += 1) {\n                        assert.strictEqual(\n                            eligibleAgreements[i].timeLeftInSubmitProofWindow,\n                            expectedAgreements[i].timeLeftInSubmitProofWindow,\n                        );\n                    }\n                };\n\n            it(\n                'returns no eligible service agreement at timestamp 49',\n                testEligibleAgreementsForSubmitProof(49, 33, []),\n            );\n            it(\n                'returns no eligible service agreement at timestamp 67',\n                testEligibleAgreementsForSubmitProof(67, 33, []),\n            );\n            it(\n                'returns no eligible service agreement at timestamp 80',\n                testEligibleAgreementsForSubmitProof(80, 33, []),\n            );\n            it(\n                'returns one eligible service agreements at timestamp 81',\n                testEligibleAgreementsForSubmitProof(81, 33, [\n                    { ...agreements[1], currentEpoch: 0, timeLeftInSubmitProofWindow: 33 },\n                ]),\n            );\n            it(\n                'returns one eligible service agreements at timestamp 92',\n                testEligibleAgreementsForSubmitProof(92, 33, [\n                    { ...agreements[1], currentEpoch: 0, timeLeftInSubmitProofWindow: 22 },\n                ]),\n            );\n            it(\n                'returns one eligible service agreements at timestamp 113',\n                testEligibleAgreementsForSubmitProof(113, 33, [\n                    { ...agreements[1], currentEpoch: 0, timeLeftInSubmitProofWindow: 1 },\n                ]),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 114',\n                testEligibleAgreementsForSubmitProof(114, 33, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 167',\n                testEligibleAgreementsForSubmitProof(167, 33, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 181',\n                testEligibleAgreementsForSubmitProof(181, 33, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 192',\n                testEligibleAgreementsForSubmitProof(192, 33, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 199',\n                testEligibleAgreementsForSubmitProof(199, 33, []),\n            );\n            it(\n                'returns no eligible service agreements at timestamp 200',\n                testEligibleAgreementsForSubmitProof(200, 33, []),\n            );\n        });\n    });\n\n    async function insertLoadTestAgreements(numAgreements) {\n        let agreements = [];\n        for (let tokenId = 0; tokenId < numAgreements; tokenId += 1) {\n            agreements.push(\n                createAgreement({\n                    tokenId,\n                    startTime: Math.floor(Math.random() * 101),\n                    lastCommitEpoch: [null, 0][Math.floor(Math.random() * 3)],\n                    lastProofEpoch: [null, 0][Math.floor(Math.random() * 3)],\n                }),\n            );\n\n            if (agreements.length % 100_000 === 0) {\n                // eslint-disable-next-line no-await-in-loop\n                await repositoryModuleManager.bulkCreateServiceAgreementRecords(agreements);\n                agreements = [];\n            }\n        }\n        if (agreements.length) {\n            await repositoryModuleManager.bulkCreateServiceAgreementRecords(agreements);\n        }\n    }\n\n    describe.skip('test load', () => {\n        describe('100_000 rows', () => {\n            beforeEach(async function t() {\n                this.timeout(0);\n                await insertLoadTestAgreements(100_000);\n            });\n\n            it('getEligibleAgreementsForSubmitCommit returns agreements in less than 100 ms', async () => {\n                const start = performance.now();\n                await repositoryModuleManager.getEligibleAgreementsForSubmitCommit(\n                    100,\n                    blockchain,\n                    25,\n                );\n                const end = performance.now();\n                const duration = end - start;\n                expect(duration).to.be.lessThan(100);\n            });\n\n            it('getEligibleAgreementsForSubmitProof returns agreements in less than 100 ms', async () => {\n                const start = performance.now();\n                await repositoryModuleManager.getEligibleAgreementsForSubmitProof(\n                    100,\n                    blockchain,\n                    33,\n                );\n                const end = performance.now();\n                const duration = end - start;\n                expect(duration).to.be.lessThan(100);\n            });\n        });\n\n        describe('1_000_000 rows', () => {\n            beforeEach(async function t() {\n                this.timeout(0);\n                await insertLoadTestAgreements(1_000_000);\n            });\n\n            it('getEligibleAgreementsForSubmitCommit returns agreements in less than 1000 ms', async () => {\n                const start = performance.now();\n                await repositoryModuleManager.getEligibleAgreementsForSubmitCommit(\n                    100,\n                    blockchain,\n                    25,\n                );\n                const end = performance.now();\n                const duration = end - start;\n                expect(duration).to.be.lessThan(1000);\n            });\n\n            it('getEligibleAgreementsForSubmitProof returns agreements in less than 1000 ms', async () => {\n                const start = performance.now();\n                await repositoryModuleManager.getEligibleAgreementsForSubmitProof(\n                    100,\n                    blockchain,\n                    33,\n                );\n                const end = performance.now();\n                const duration = end - start;\n                expect(duration).to.be.lessThan(1000);\n            });\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/modules/triple-store/config.json",
    "content": "{\n    \"modules\": {\n        \"tripleStore\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"ot-blazegraph\": {\n                    \"enabled\": true,\n                    \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                    \"config\": {\n                        \"repositories\": {\n                            \"privateCurrent\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"triple-store-test-private-current\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            },\n                            \"privateHistory\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"triple-store-test-private-history\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            },\n                            \"publicCurrent\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"triple-store-test-public-current\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            },\n                            \"publicHistory\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"triple-store-test-public-history\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "test/unit/modules/triple-store/triple-store.test.js",
    "content": "/* import { describe, it, before, beforeEach } from 'mocha';\nimport chai from 'chai';\nimport { readFile } from 'fs/promises';\nimport { formatAssertion, calculateRoot } from 'assertion-tools';\nimport { TRIPLE_STORE_REPOSITORIES } from '../../../src/constants/constants.js';\nimport Logger from '../../../src/logger/logger.js';\nimport TripleStoreModuleManager from '../../../src/modules/triple-store/triple-store-module-manager.js';\nimport DataService from '../../../src/service/data-service.js';\nimport assertions from '../../assertions/assertions.js';\n\nconst { assert } = chai;\n\nlet logger;\nlet tripleStoreModuleManager;\nlet dataService;\nconst config = JSON.parse(await readFile('./test/modules/triple-store/config.json'));\nconst implementationName = 'ot-blazegraph';\n\nasync function _insertAndGet(content) {\n    const assertion = await formatAssertion(content);\n    const assertionId = calculateRoot(assertion);\n\n    await tripleStoreModuleManager.insertKnowledgeAssets(\n        implementationName,\n        TRIPLE_STORE_REPOSITORIES.PUBLIC_CURRENT,\n        assertionId,\n        assertion.join('\\n'),\n    );\n\n    const nquads = await tripleStoreModuleManager.getKnowledgeCollection(\n        implementationName,\n        TRIPLE_STORE_REPOSITORIES.PUBLIC_CURRENT,\n        assertionId,\n    );\n\n    const retrievedAssertion = await dataService.toNQuads(nquads, 'application/n-quads');\n    const retrievedAssertionId = calculateRoot(retrievedAssertion);\n\n    assert.deepEqual(retrievedAssertion, assertion, `assertions are not equal`);\n    assert.equal(retrievedAssertionId, assertionId, `assertion ids are not equal`);\n}\n\ndescribe('Triple store module', () => {\n    before('Initialize logger', () => {\n        logger = new Logger('trace');\n        logger.info = () => {};\n    });\n    beforeEach('Initialize triple store module manager', async () => {\n        tripleStoreModuleManager = new TripleStoreModuleManager({\n            config,\n            logger,\n        });\n        await tripleStoreModuleManager.initialize();\n\n        const implementation = tripleStoreModuleManager.getImplementation(implementationName);\n        await Promise.all(\n            Object.keys(implementation.config.repositories).map((repository) =>\n                implementation.module.deleteRepository(repository),\n            ),\n        );\n\n        await tripleStoreModuleManager.initialize();\n    });\n    before('Initialize data service', async () => {\n        dataService = new DataService({\n            logger,\n        });\n    });\n    describe('Insert and get return same assertions:', async () => {\n        for (const assertionName in assertions) {\n            it(`${assertionName}`, () => _insertAndGet(assertions[assertionName]));\n        }\n    });\n});\n */\n"
  },
  {
    "path": "test/unit/modules/validation/config.json",
    "content": "{\n  \"modules\":{\n    \"validation\":{\n      \"enabled\":true,\n      \"implementation\":{\n        \"merkle-validation\":{\n          \"enabled\":true,\n          \"package\":\"./validation/implementation/merkle-validation.js\",\n          \"config\":{}\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/unit/modules/validation/validation-module-manager.test.js",
    "content": "import { describe, it, beforeEach } from 'mocha';\nimport { expect, assert } from 'chai';\nimport { readFile } from 'fs/promises';\nimport { calculateRoot } from 'assertion-tools';\nimport ValidationModuleManager from '../../../../src/modules/validation/validation-module-manager.js';\nimport Logger from '../../../../src/logger/logger.js';\n\nlet validationManager;\n\nconst config = JSON.parse(await readFile('./test/unit/modules/validation/config.json', 'utf-8'));\nconst assertion = [\n    {\n        '@context': 'https://schema.org',\n        '@id': 'https://tesla.modelX/2321',\n        '@type': 'Car',\n        name: 'Tesla Model X',\n        brand: {\n            '@type': 'Brand',\n            name: 'Tesla',\n        },\n        model: 'Model X',\n        manufacturer: {\n            '@type': 'Organization',\n            name: 'Tesla, Inc.',\n        },\n        fuelType: 'Electric',\n    },\n];\nconst invalidValues = [null, undefined];\nconst hashFunctionId = 1;\nconst keyword =\n    '0xB0D4afd8879eD9F52b28595d31B441D079B2Ca0768e44dc71bf509adfccbea9df949f253afa56796a3a926203f90a1e4914247d3';\n\ndescribe.only('Validation module manager', async () => {\n    beforeEach('initialize validation module manage', async () => {\n        validationManager = new ValidationModuleManager({\n            config,\n            logger: new Logger(),\n        });\n\n        validationManager.initialized = true;\n        expect(await validationManager.initialize()).to.be.true;\n    });\n\n    it('validates module name is as expected', async () => {\n        const moduleName = await validationManager.getName();\n\n        expect(moduleName).to.equal('validation');\n    });\n\n    it('validate successful root hash calculation, expect to be matched', async () => {\n        const expectedRootHash = await calculateRoot(assertion);\n        const calculatedRootHash = await validationManager.calculateRoot(assertion);\n\n        assert(expect(calculatedRootHash).to.exist);\n        expect(calculatedRootHash).to.equal(expectedRootHash);\n    });\n\n    it('root hash cannot be calculated without initialization', async () => {\n        validationManager.initialized = false;\n\n        try {\n            await validationManager.calculateRoot(assertion);\n        } catch (error) {\n            expect(error.message).to.equal('Validation module is not initialized.');\n        }\n    });\n\n    it('root hash calculation failed when assertion is null or undefined', async () => {\n        invalidValues.forEach((value) => {\n            expect(() => validationManager.calculateRoot(value)).to.throw(\n                Error,\n                'Calculation failed: Assertion cannot be null or undefined.',\n            );\n        });\n    });\n\n    it('successful getting merkle proof hash', async () => {\n        const calculatedMerkleHash = await validationManager.getMerkleProof(assertion, 0);\n\n        assert(expect(calculatedMerkleHash).to.exist);\n        expect(calculatedMerkleHash).to.be.instanceof(Object);\n        expect(calculatedMerkleHash).to.haveOwnProperty('leaf').and.to.be.a('string');\n        expect(calculatedMerkleHash).to.haveOwnProperty('proof').and.to.be.a('array');\n    });\n\n    it('merkle prof hash cannot be calculated without initialization', async () => {\n        validationManager.initialized = false;\n\n        try {\n            await validationManager.getMerkleProof(assertion, 0);\n        } catch (error) {\n            expect(error.message).to.equal('Validation module is not initialized.');\n        }\n    });\n\n    it('failed merkle proof hash calculation when assertion is null or undefined', async () => {\n        for (const value of invalidValues) {\n            // eslint-disable-next-line no-await-in-loop\n            expect(await validationManager.getMerkleProof(value, 0)).to.be.rejectedWith(\n                Error,\n                'Get merkle proof failed: Assertion cannot be null or undefined.',\n            );\n        }\n    });\n\n    it('validate getting function name', async () => {\n        const getFnHashName = validationManager.getHashFunctionName(hashFunctionId);\n\n        assert(expect(getFnHashName).to.exist);\n        expect(getFnHashName).to.equal('sha256');\n    });\n\n    it('failed getting function name without initialization', async () => {\n        validationManager.initialized = false;\n\n        try {\n            validationManager.getHashFunctionName(hashFunctionId);\n        } catch (error) {\n            expect(error.message).to.equal('Validation module is not initialized.');\n        }\n    });\n\n    it('validate successful calling function name', async () => {\n        const callFunction = await validationManager.callHashFunction(hashFunctionId, keyword);\n\n        assert(expect(callFunction).to.exist);\n        expect(callFunction).to.be.a('string');\n        expect(callFunction).to.equal(\n            '0x5fe7425e0d956e2cafeac276c3ee8e70f377b2bd14790bc6d4777c3e7ba63b46',\n        );\n    });\n\n    it('unsuccessful calling function name without initialization', async () => {\n        validationManager.initialized = false;\n\n        try {\n            await validationManager.callHashFunction(hashFunctionId, keyword);\n        } catch (error) {\n            expect(error.message).to.equal('Validation module is not initialized.');\n        }\n    });\n\n    it('failed function name initialization when function id is null or undefined', async () => {\n        async function testInvalidValues() {\n            for (const value of invalidValues) {\n                try {\n                    // eslint-disable-next-line no-await-in-loop\n                    await validationManager.getMerkleProof(value, 0);\n                } catch (error) {\n                    expect(error.message).to.equal(\n                        'Get merkle proof failed: Assertion cannot be null or undefined.',\n                    );\n                }\n            }\n        }\n\n        await testInvalidValues();\n    });\n\n    it('failed function name initialization when data is null or undefined', async () => {\n        async function testInvalidValues() {\n            for (const value of invalidValues) {\n                try {\n                    // eslint-disable-next-line no-await-in-loop\n                    await validationManager.callHashFunction(value, 0);\n                } catch (error) {\n                    expect(error.message).to.equal(\n                        'Calling hash fn failed: Values cannot be null or undefined.',\n                    );\n                }\n            }\n        }\n\n        await testInvalidValues();\n    });\n});\n"
  },
  {
    "path": "test/unit/service/auth-service.test.js",
    "content": "import 'dotenv/config';\nimport { expect } from 'chai';\nimport { describe, it, afterEach } from 'mocha';\nimport { v4 as uuid } from 'uuid';\nimport sinon from 'sinon';\nimport AuthService from '../../../src/service/auth-service.js';\nimport jwtUtil from '../../../src/service/util/jwt-util.js';\nimport RepositoryModuleManager from '../../../src/modules/repository/repository-module-manager.js';\n\nconst whitelistedIps = [\n    '::1',\n    '127.0.0.1',\n    '54.31.28.8',\n    'a3c6:3c39:492c:831b:d1a1:7944:b984:f32a',\n];\nconst invalidIps = ['247.8.32.50', null, undefined, {}, [], true, false, 123, '123...', NaN];\nconst invalidTokens = ['token', 12345, {}, [], undefined, null, true, false, '123.321.32', NaN];\n\nconst tests = [\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: false,\n        ipValid: false,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: true,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: false,\n        ipValid: false,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: false,\n        ipValid: true,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: true,\n    },\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: true,\n        ipValid: false,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: true,\n        ipValid: true,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: true,\n        ipValid: false,\n        tokenValid: true,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: true,\n    },\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: true,\n        ipValid: false,\n        tokenValid: true,\n        tokenRevoked: true,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: true,\n        ipValid: false,\n        tokenValid: true,\n        tokenRevoked: false,\n        tokenExpired: true,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: false,\n        tokenAuthEnabled: true,\n        ipValid: false,\n        tokenValid: true,\n        tokenRevoked: true,\n        tokenExpired: true,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: true,\n        ipValid: false,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: true,\n        ipValid: true,\n        tokenValid: false,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: true,\n        ipValid: true,\n        tokenValid: true,\n        tokenRevoked: false,\n        tokenExpired: false,\n        expected: true,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: true,\n        ipValid: true,\n        tokenValid: true,\n        tokenRevoked: true,\n        tokenExpired: false,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: true,\n        ipValid: true,\n        tokenValid: true,\n        tokenRevoked: false,\n        tokenExpired: true,\n        expected: false,\n    },\n    {\n        ipAuthEnabled: true,\n        tokenAuthEnabled: true,\n        ipValid: true,\n        tokenValid: true,\n        tokenRevoked: true,\n        tokenExpired: true,\n        expected: false,\n    },\n];\n\nconst configObj = {\n    auth: {\n        ipWhitelist: whitelistedIps,\n        publicOperations: ['QUERY'],\n    },\n};\n\nconst getConfig = (ipAuthEnabled, tokenAuthEnabled) => {\n    const configClone = JSON.parse(JSON.stringify(configObj));\n\n    configClone.auth.ipBasedAuthEnabled = ipAuthEnabled;\n    configClone.auth.tokenBasedAuthEnabled = tokenAuthEnabled;\n\n    return configClone;\n};\n\nconst getRepository = (isTokenRevoked, tokenAbilitiesValid) =>\n    sinon.createStubInstance(RepositoryModuleManager, {\n        isTokenRevoked,\n        getTokenAbilities: tokenAbilitiesValid ? ['QUERY', 'PUBLISH', 'SEARCH'] : [],\n    });\n\nconst getIps = (isValid) => {\n    if (isValid) {\n        return whitelistedIps;\n    }\n\n    return invalidIps;\n};\n\nconst getTokens = (isValid, isExpired) => {\n    if (isValid) {\n        if (isExpired) {\n            return [jwtUtil.generateJWT(uuid(), '-2d')];\n        }\n        return [jwtUtil.generateJWT(uuid())];\n    }\n\n    return invalidTokens;\n};\n\ndescribe('authenticate()', async () => {\n    afterEach(() => {\n        sinon.restore();\n    });\n\n    for (const t of tests) {\n        let testText = '';\n\n        for (const field in t) {\n            if (field === 'expected') {\n                testText += `${field.toUpperCase()}: ${t[field]}`;\n            } else {\n                testText += `${field}: ${t[field]} | `;\n            }\n        }\n\n        it(testText, async () => {\n            const config = getConfig(t.ipAuthEnabled, t.tokenAuthEnabled);\n            const repositoryModuleManager = getRepository(t.tokenRevoked);\n            const ips = getIps(t.ipValid);\n            const tokens = getTokens(t.tokenValid, t.tokenExpired);\n            const authService = new AuthService({ config, repositoryModuleManager });\n\n            for (const ip of ips) {\n                for (const token of tokens) {\n                    // eslint-disable-next-line no-await-in-loop\n                    const isAuthenticated = await authService.authenticate(ip, token);\n                    expect(isAuthenticated).to.be.equal(t.expected);\n                }\n            }\n        });\n    }\n\n    it('returns false if token is valid but is not found in the database', async () => {\n        const config = getConfig(false, true);\n        const repositoryModuleManager = getRepository(null, true);\n        const [token] = getTokens(true);\n        const authService = new AuthService({ config, repositoryModuleManager });\n\n        const isAuthenticated = await authService.authenticate('', token);\n        expect(isAuthenticated).to.be.false;\n    });\n});\n\ndescribe('isAuthorized()', async () => {\n    afterEach(() => {\n        sinon.restore();\n    });\n\n    it('returns true if tokenBasedAuthentication is disabled', async () => {\n        const config = getConfig(false, false);\n        const authService = new AuthService({ config });\n\n        const isAuthorized = await authService.isAuthorized(null, null);\n        expect(isAuthorized).to.be.equal(true);\n    });\n\n    it('returns true if user has ability to perform an action', async () => {\n        const config = getConfig(false, true);\n        const repositoryModuleManager = getRepository(false, true);\n        const jwt = jwtUtil.generateJWT(uuid());\n        const authService = new AuthService({ config, repositoryModuleManager });\n\n        const isAuthorized = await authService.isAuthorized(jwt, 'QUERY');\n        expect(isAuthorized).to.be.equal(true);\n    });\n\n    it(\"returns false if user doesn't have ability to perform an action\", async () => {\n        const config = getConfig(false, true);\n        const jwt = jwtUtil.generateJWT(uuid());\n\n        const authService = new AuthService({\n            config,\n            repositoryModuleManager: getRepository(false, true),\n        });\n        const isAuthorized = await authService.isAuthorized(jwt, 'OPERATION');\n        expect(isAuthorized).to.be.equal(false);\n    });\n\n    it('returns false if user roles are not found', async () => {\n        const config = getConfig(false, true);\n        const jwt = jwtUtil.generateJWT(uuid());\n\n        const authService = new AuthService({\n            config,\n            repositoryModuleManager: getRepository(false, false),\n        });\n\n        const isAuthorized = await authService.isAuthorized(jwt, 'PUBLISH');\n        expect(isAuthorized).to.be.equal(false);\n    });\n});\n\ndescribe('isPublicOperation()', async () => {\n    afterEach(() => {\n        sinon.restore();\n    });\n\n    it('returns true if route is public', async () => {\n        const config = getConfig(false, false);\n        const authService = new AuthService({ config });\n\n        const isPublic = authService.isPublicOperation('QUERY');\n        expect(isPublic).to.be.equal(true);\n    });\n\n    it('returns false if route is not public', async () => {\n        const config = getConfig(false, false, true);\n        const authService = new AuthService({ config });\n\n        const isPublic = authService.isPublicOperation('PUBLISH');\n        expect(isPublic).to.be.equal(false);\n    });\n\n    it('returns false if public routes are not defined', async () => {\n        const config = getConfig(false, false, true);\n        config.auth.publicOperations = undefined;\n        const authService = new AuthService({ config });\n\n        const isPublic = authService.isPublicOperation('PUBLISH');\n        expect(isPublic).to.be.equal(false);\n    });\n});\n"
  },
  {
    "path": "test/unit/service/get-service.test.js",
    "content": "import { beforeEach, afterEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport sinon from 'sinon';\nimport { OPERATION_REQUEST_STATUS } from '../../../src/constants/constants.js';\nimport RepositoryModuleManagerMock from '../mock/repository-module-manager-mock.js';\nimport ValidationModuleManagerMock from '../mock/validation-module-manager-mock.js';\nimport BlockchainModuleManagerMock from '../mock/blockchain-module-manager-mock.js';\nimport OperationIdServiceMock from '../mock/operation-id-service-mock.js';\nimport CommandExecutorMock from '../mock/command-executor-mock.js';\nimport GetService from '../../../src/service/get-service.js';\nimport Logger from '../../../src/logger/logger.js';\n\nlet getService;\nlet cacheOperationIdDataSpy;\nlet commandExecutorAddSpy;\n\ndescribe('Get service test', async () => {\n    beforeEach(() => {\n        const repositoryModuleManagerMock = new RepositoryModuleManagerMock();\n\n        getService = new GetService({\n            repositoryModuleManager: repositoryModuleManagerMock,\n            operationIdService: new OperationIdServiceMock({\n                repositoryModuleManager: repositoryModuleManagerMock,\n            }),\n            commandExecutor: new CommandExecutorMock(),\n            validationModuleManager: new ValidationModuleManagerMock(),\n            blockchainModuleManager: new BlockchainModuleManagerMock(),\n            logger: new Logger(),\n        });\n        cacheOperationIdDataSpy = sinon.spy(getService.operationIdService, 'cacheOperationIdData');\n        commandExecutorAddSpy = sinon.spy(getService.commandExecutor, 'add');\n    });\n\n    afterEach(() => {\n        cacheOperationIdDataSpy.restore();\n        commandExecutorAddSpy.restore();\n    });\n\n    it('Completed get completes with low ACK ask', async () => {\n        await getService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 1,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {\n                nquads: '<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green-goblin> <http://example.org/graphs/spiderman> .',\n            },\n        );\n\n        const returnedResponses = getService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            cacheOperationIdDataSpy.calledWith('5195d01a-b437-4aae-b388-a77b9fa715f1', {\n                assertion:\n                    '<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green-goblin> <http://example.org/graphs/spiderman> .',\n            }),\n        ).to.be.true;\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.COMPLETED,\n        ).to.be.true;\n    });\n\n    it('Completed get leads to scheduling operation for leftover nodes and status stays same', async () => {\n        await getService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [1, 2, 3, 4],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 12,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = getService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            commandExecutorAddSpy.calledWith('5195d01a-b437-4aae-b388-a77b9fa715f1', [1, 2, 3, 4]),\n        );\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.COMPLETED,\n        ).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/service/operation-id-service-cache.test.js",
    "content": "import { describe, it, beforeEach, afterEach } from 'mocha';\nimport { expect } from 'chai';\nimport fs from 'fs/promises';\nimport path from 'path';\nimport os from 'os';\nimport OperationIdService from '../../../src/service/operation-id-service.js';\n\ndescribe('OperationIdService file cache cleanup', () => {\n    let tmpDir;\n    let service;\n\n    beforeEach(async () => {\n        tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opid-cache-'));\n        const now = Date.now();\n\n        // Older than TTL (2 hours)\n        const oldFile = path.join(tmpDir, 'old.json');\n        await fs.writeFile(oldFile, '{}');\n        await fs.utimes(\n            oldFile,\n            new Date(now - 2 * 60 * 60 * 1000),\n            new Date(now - 2 * 60 * 60 * 1000),\n        );\n\n        // Newer than TTL (10 minutes)\n        const newFile = path.join(tmpDir, 'new.json');\n        await fs.writeFile(newFile, '{}');\n        await fs.utimes(newFile, new Date(now - 10 * 60 * 1000), new Date(now - 10 * 60 * 1000));\n\n        const fileService = {\n            getOperationIdCachePath: () => tmpDir,\n            async pathExists(p) {\n                try {\n                    await fs.stat(p);\n                    return true;\n                } catch {\n                    return false;\n                }\n            },\n            readDirectory: (p) => fs.readdir(p),\n            stat: (p) => fs.stat(p),\n            removeFile: (p) => fs.rm(p, { force: true }),\n        };\n\n        service = new OperationIdService({\n            logger: { debug: () => {}, warn: () => {}, error: () => {} },\n            fileService,\n            repositoryModuleManager: {},\n            eventEmitter: { emit: () => {} },\n        });\n    });\n\n    afterEach(async () => {\n        await fs.rm(tmpDir, { recursive: true, force: true });\n    });\n\n    it('removes only files older than TTL', async () => {\n        const deleted = await service.removeExpiredOperationIdFileCache(60 * 60 * 1000, 10);\n        const remainingFiles = await fs.readdir(tmpDir);\n\n        expect(deleted).to.equal(1);\n        expect(remainingFiles).to.deep.equal(['new.json']);\n    });\n});\n"
  },
  {
    "path": "test/unit/service/operation-service.test.js",
    "content": "import { beforeEach, afterEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport sinon from 'sinon';\nimport { OPERATION_REQUEST_STATUS } from '../../../src/constants/constants.js';\nimport RepositoryModuleManagerMock from '../mock/repository-module-manager-mock.js';\nimport ValidationModuleManagerMock from '../mock/validation-module-manager-mock.js';\nimport BlockchainModuleManagerMock from '../mock/blockchain-module-manager-mock.js';\nimport OperationIdServiceMock from '../mock/operation-id-service-mock.js';\nimport CommandExecutorMock from '../mock/command-executor-mock.js';\nimport PublishService from '../../../src/service/publish-service.js';\nimport Logger from '../../../src/logger/logger.js';\n\nlet publishService;\nlet cacheOperationIdDataSpy;\nlet commandExecutorAddSpy;\n\ndescribe('Operation service test', async () => {\n    beforeEach(() => {\n        const repositoryModuleManagerMock = new RepositoryModuleManagerMock();\n\n        publishService = new PublishService({\n            repositoryModuleManager: repositoryModuleManagerMock,\n            operationIdService: new OperationIdServiceMock({\n                repositoryModuleManager: repositoryModuleManagerMock,\n            }),\n            commandExecutor: new CommandExecutorMock(),\n            validationModuleManager: new ValidationModuleManagerMock(),\n            blockchainModuleManager: new BlockchainModuleManagerMock(),\n            logger: new Logger(),\n        });\n        cacheOperationIdDataSpy = sinon.spy(\n            publishService.operationIdService,\n            'cacheOperationIdData',\n        );\n        commandExecutorAddSpy = sinon.spy(publishService.commandExecutor, 'add');\n    });\n\n    afterEach(() => {\n        cacheOperationIdDataSpy.restore();\n        commandExecutorAddSpy.restore();\n    });\n\n    it('Creates a response record and returns status for each keyword', async () => {\n        await publishService.getResponsesStatuses(\n            OPERATION_REQUEST_STATUS.FAILED,\n            null,\n            '5195d01a-b437-4aae-b388-a77b9fa715f1',\n            'origintrail',\n        );\n\n        const returnedResponses = await publishService.getResponsesStatuses(\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            null,\n            '5195d01a-b437-4aae-b388-a77b9fa715f1',\n            'origintrail',\n        );\n\n        // Do two calls to make sure the state has persisted after the first one\n\n        expect(returnedResponses).to.deep.equal({\n            origintrail: { failedNumber: 1, completedNumber: 1 },\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/service/publish-service.test.js",
    "content": "import { beforeEach, afterEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport sinon from 'sinon';\nimport { OPERATION_REQUEST_STATUS } from '../../../src/constants/constants.js';\nimport RepositoryModuleManagerMock from '../mock/repository-module-manager-mock.js';\nimport ValidationModuleManagerMock from '../mock/validation-module-manager-mock.js';\nimport BlockchainModuleManagerMock from '../mock/blockchain-module-manager-mock.js';\nimport OperationIdServiceMock from '../mock/operation-id-service-mock.js';\nimport CommandExecutorMock from '../mock/command-executor-mock.js';\nimport PublishService from '../../../src/service/publish-service.js';\nimport Logger from '../../../src/logger/logger.js';\n\nlet publishService;\nlet cacheOperationIdDataSpy;\nlet commandExecutorAddSpy;\n\ndescribe('Publish service test', async () => {\n    beforeEach(() => {\n        const repositoryModuleManagerMock = new RepositoryModuleManagerMock();\n\n        publishService = new PublishService({\n            repositoryModuleManager: repositoryModuleManagerMock,\n            operationIdService: new OperationIdServiceMock({\n                repositoryModuleManager: repositoryModuleManagerMock,\n            }),\n            commandExecutor: new CommandExecutorMock(),\n            validationModuleManager: new ValidationModuleManagerMock(),\n            blockchainModuleManager: new BlockchainModuleManagerMock(),\n            logger: new Logger(),\n        });\n        cacheOperationIdDataSpy = sinon.spy(\n            publishService.operationIdService,\n            'cacheOperationIdData',\n        );\n        commandExecutorAddSpy = sinon.spy(publishService.commandExecutor, 'add');\n    });\n\n    afterEach(() => {\n        cacheOperationIdDataSpy.restore();\n        commandExecutorAddSpy.restore();\n    });\n\n    it('Completed publish completes with low ACK ask', async () => {\n        await publishService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 1,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = publishService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(cacheOperationIdDataSpy.calledWith('5195d01a-b437-4aae-b388-a77b9fa715f1', {})).to.be\n            .false;\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.COMPLETED,\n        ).to.be.true;\n    });\n\n    it('Completed publish fails with high ACK ask', async () => {\n        await publishService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 12,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = publishService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.FAILED,\n        ).to.be.true;\n    });\n\n    it('Failed publish fails with low ACK ask', async () => {\n        await publishService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 1,\n                },\n            },\n            OPERATION_REQUEST_STATUS.FAILED,\n            {},\n        );\n\n        const returnedResponses = publishService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.FAILED,\n        ).to.be.true;\n    });\n\n    it('Completed publish leads to scheduling operation for leftover nodes and status stays same', async () => {\n        await publishService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [1, 2, 3, 4],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 12,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = publishService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            commandExecutorAddSpy.calledWith('5195d01a-b437-4aae-b388-a77b9fa715f1', [1, 2, 3, 4]),\n        );\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.COMPLETED,\n        ).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/service/sharding-table-service.test.js",
    "content": "import { beforeEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport ShardingTableService from '../../../src/service/sharding-table-service.js';\nimport BlockchainModuleManagerMock from '../mock/blockchain-module-manager-mock.js';\nimport RepositoryModuleManagerMock from '../mock/repository-module-manager-mock.js';\nimport NetworkModuleManagerMock from '../mock/network-module-manager-mock.js';\nimport ValidationModuleManagerMock from '../mock/validation-module-manager-mock.js';\nimport EventEmitterMock from '../mock/event-emitter-mock.js';\nimport { BYTES_IN_KILOBYTE } from '../../../src/constants/constants.js';\n\nlet shardingTableService;\n\ndescribe('Sharding table service test', async () => {\n    beforeEach(() => {\n        shardingTableService = new ShardingTableService({\n            blockchainModuleManager: new BlockchainModuleManagerMock(),\n            repositoryModuleManager: new RepositoryModuleManagerMock(),\n            networkModuleManager: new NetworkModuleManagerMock(),\n            validationModuleManager: new ValidationModuleManagerMock(),\n            eventEmitter: new EventEmitterMock(),\n        });\n    });\n\n    it('Get bid suggestion, returns bid suggestion successfully', async () => {\n        const epochsNumber = 5;\n        const assertionSize = BYTES_IN_KILOBYTE;\n        const contentAssetStorageAddress = '0xABd59A9aa71847F499d624c492d3903dA953d67a';\n        const firstAssertionId =\n            '0xb44062de45333119471934bc0340c05ff09c0b463392384bc2030cd0a20c334b';\n        const hashFunctionId = 1;\n        const bidSuggestions = await shardingTableService.getBidSuggestion(\n            'ganache',\n            epochsNumber,\n            assertionSize,\n            contentAssetStorageAddress,\n            firstAssertionId,\n            hashFunctionId,\n        );\n        expect(bidSuggestions).to.be.equal('3788323225298705400');\n    });\n\n    it('Get bid suggestion, returns valid value for assertion size 1b and ask 1 wei', async () => {\n        const epochsNumber = 5;\n        const contentAssetStorageAddress = '0xABd59A9aa71847F499d624c492d3903dA953d67a';\n        const firstAssertionId =\n            '0xb44062de45333119471934bc0340c05ff09c0b463392384bc2030cd0a20c334b';\n        const hashFunctionId = 1;\n        const askInWei = '0.000000000000000001';\n        const peers = shardingTableService.repositoryModuleManager.getAllPeerRecords();\n        shardingTableService.repositoryModuleManager.getAllPeerRecords = () => {\n            peers.forEach((peer) => {\n                // eslint-disable-next-line no-param-reassign\n                peer.ask = askInWei;\n            });\n            return peers;\n        };\n        const bidSuggestion1B = await shardingTableService.getBidSuggestion(\n            'ganache',\n            epochsNumber,\n            1,\n            contentAssetStorageAddress,\n            firstAssertionId,\n            hashFunctionId,\n        );\n        expect(bidSuggestion1B).to.be.equal('15');\n        const bidSuggestion10B = await shardingTableService.getBidSuggestion(\n            'ganache',\n            epochsNumber,\n            10,\n            contentAssetStorageAddress,\n            firstAssertionId,\n            hashFunctionId,\n        );\n        expect(bidSuggestion10B).to.be.equal('15');\n        const bidSuggestion1024B = await shardingTableService.getBidSuggestion(\n            'ganache',\n            epochsNumber,\n            1024,\n            contentAssetStorageAddress,\n            firstAssertionId,\n            hashFunctionId,\n        );\n        expect(bidSuggestion1024B).to.be.equal('15');\n        const bidSuggestion2048B = await shardingTableService.getBidSuggestion(\n            'ganache',\n            epochsNumber,\n            2048,\n            contentAssetStorageAddress,\n            firstAssertionId,\n            hashFunctionId,\n        );\n        expect(bidSuggestion2048B).to.be.equal('30');\n    });\n});\n"
  },
  {
    "path": "test/unit/service/update-service.test.js",
    "content": "import { beforeEach, afterEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport sinon from 'sinon';\nimport { OPERATION_REQUEST_STATUS } from '../../../src/constants/constants.js';\nimport RepositoryModuleManagerMock from '../mock/repository-module-manager-mock.js';\nimport ValidationModuleManagerMock from '../mock/validation-module-manager-mock.js';\nimport BlockchainModuleManagerMock from '../mock/blockchain-module-manager-mock.js';\nimport OperationIdServiceMock from '../mock/operation-id-service-mock.js';\nimport CommandExecutorMock from '../mock/command-executor-mock.js';\nimport UpdateService from '../../../src/service/update-service.js';\nimport Logger from '../../../src/logger/logger.js';\n\nlet updateService;\nlet cacheOperationIdDataSpy;\nlet commandExecutorAddSpy;\n\ndescribe('Update service test', async () => {\n    beforeEach(() => {\n        const repositoryModuleManagerMock = new RepositoryModuleManagerMock();\n\n        updateService = new UpdateService({\n            repositoryModuleManager: repositoryModuleManagerMock,\n            operationIdService: new OperationIdServiceMock({\n                repositoryModuleManager: repositoryModuleManagerMock,\n            }),\n            commandExecutor: new CommandExecutorMock(),\n            validationModuleManager: new ValidationModuleManagerMock(),\n            blockchainModuleManager: new BlockchainModuleManagerMock(),\n            logger: new Logger(),\n        });\n\n        cacheOperationIdDataSpy = sinon.spy(\n            updateService.operationIdService,\n            'cacheOperationIdData',\n        );\n        commandExecutorAddSpy = sinon.spy(updateService.commandExecutor, 'add');\n    });\n\n    afterEach(() => {\n        cacheOperationIdDataSpy.restore();\n        commandExecutorAddSpy.restore();\n    });\n\n    it('Completed update completes with low ACK ask', async () => {\n        await updateService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 1,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = updateService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(cacheOperationIdDataSpy.calledWith('5195d01a-b437-4aae-b388-a77b9fa715f1', {})).to.be\n            .false;\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.COMPLETED,\n        ).to.be.true;\n    });\n\n    it('Completed update fails with high ACK ask', async () => {\n        await updateService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 12,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = updateService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.FAILED,\n        ).to.be.true;\n    });\n\n    it('Failed update fails with low ACK ask', async () => {\n        await updateService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 1,\n                },\n            },\n            OPERATION_REQUEST_STATUS.FAILED,\n            {},\n        );\n\n        const returnedResponses = updateService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.FAILED,\n        ).to.be.true;\n    });\n\n    it('Completed update leads to scheduling operation for leftover nodes and status stays same', async () => {\n        await updateService.processResponse(\n            {\n                data: {\n                    operationId: '5195d01a-b437-4aae-b388-a77b9fa715f1',\n                    blockchain: 'hardhat',\n                    numberOfFoundNodes: 1,\n                    leftoverNodes: [1, 2, 3, 4],\n                    keyword: 'origintrail',\n                    batchSize: 10,\n                    minAckResponses: 12,\n                },\n            },\n            OPERATION_REQUEST_STATUS.COMPLETED,\n            {},\n        );\n\n        const returnedResponses = updateService.repositoryModuleManager.getAllResponseStatuses();\n\n        expect(returnedResponses.length).to.be.equal(2);\n\n        expect(\n            commandExecutorAddSpy.calledWith('5195d01a-b437-4aae-b388-a77b9fa715f1', [1, 2, 3, 4]),\n        );\n\n        expect(\n            returnedResponses[returnedResponses.length - 1].status ===\n                OPERATION_REQUEST_STATUS.COMPLETED,\n        ).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/service/util/jwt-util.test.js",
    "content": "import 'dotenv/config';\nimport { v4 as uuid } from 'uuid';\nimport { expect } from 'chai';\nimport { describe, it } from 'mocha';\nimport jwtUtil from '../../../../src/service/util/jwt-util.js';\n\nconst getPayload = (token) => {\n    const b64Payload = token.split('.')[1];\n\n    return JSON.parse(Buffer.from(b64Payload, 'base64').toString());\n};\n\nconst nonJwts = [\n    '123',\n    214214124124,\n    'header.payload.signature',\n    null,\n    undefined,\n    true,\n    false,\n    {},\n    [],\n];\n\ndescribe('Auth JWT generation', async () => {\n    it('generates JWT token with tokenId payload', () => {\n        const tokenId = uuid();\n        const token = jwtUtil.generateJWT(tokenId);\n\n        expect(token).not.be.null;\n        expect(getPayload(token).jti).to.be.equal(tokenId);\n    });\n\n    it('generates null if invalid tokenId is provided', () => {\n        const nonUuids = [true, false, undefined, null, {}, [], 'string', 123];\n\n        for (const val of nonUuids) {\n            const token = jwtUtil.generateJWT(val);\n            expect(token).to.be.null;\n        }\n    });\n\n    it('generates payload without expiration date if expiresIn argument is not provided', () => {\n        const token = jwtUtil.generateJWT(uuid());\n        expect(getPayload(token).exp).to.be.undefined;\n    });\n\n    it('generates payload with expiration date if expiresIn argument is  provided', () => {\n        const token = jwtUtil.generateJWT(uuid(), '2d');\n        expect(getPayload(token).exp).to.be.ok;\n    });\n});\n\ndescribe('JWT token validation', async () => {\n    it('returns true if JWT is valid', async () => {\n        const token = jwtUtil.generateJWT(uuid());\n        expect(jwtUtil.validateJWT(token)).to.be.true;\n    });\n\n    it('returns true if JWT is expired', async () => {\n        const token = jwtUtil.generateJWT(uuid(), '-2d');\n        expect(jwtUtil.validateJWT(token)).to.be.false;\n    });\n\n    it('returns false if JWT is not valid', async () => {\n        const token =\n            'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';\n        expect(jwtUtil.validateJWT(token)).to.be.false;\n    });\n\n    it('returns false if non JWT value is passed', async () => {\n        for (const val of nonJwts) {\n            expect(jwtUtil.validateJWT(val)).to.be.false;\n        }\n    });\n});\n\ndescribe('JWT payload extracting', async () => {\n    it('returns JWT payload if valid token is provided', async () => {\n        const token = jwtUtil.generateJWT(uuid());\n\n        expect(jwtUtil.getPayload(token)).to.be.ok;\n    });\n\n    it('returns null if invalid token is provided', async () => {\n        for (const val of nonJwts) {\n            expect(jwtUtil.getPayload(val)).to.be.null;\n        }\n    });\n});\n\ndescribe('JWT decoding', async () => {\n    it('returns decoded JWT (header, payload, signature) if valid token is provided', async () => {\n        const token = jwtUtil.generateJWT(uuid());\n\n        expect(jwtUtil.decode(token).header).to.be.ok;\n        expect(jwtUtil.decode(token).payload).to.be.ok;\n        expect(jwtUtil.decode(token).signature).to.be.ok;\n    });\n\n    it('returns null if invalid token is provided', async () => {\n        for (const val of nonJwts) {\n            expect(jwtUtil.decode(val)).to.be.null;\n        }\n    });\n});\n"
  },
  {
    "path": "test/unit/service/validation-service.test.js",
    "content": "import { beforeEach, describe, it } from 'mocha';\nimport { expect } from 'chai';\nimport ValidationModuleManagerMock from '../mock/validation-module-manager-mock.js';\nimport BlockchainModuleManagerMock from '../mock/blockchain-module-manager-mock.js';\nimport ValidationService from '../../../src/service/validation-service.js';\nimport Logger from '../../../src/logger/logger.js';\n\nlet validationService;\n\ndescribe('Validation service test', async () => {\n    beforeEach(() => {\n        validationService = new ValidationService({\n            validationModuleManager: new ValidationModuleManagerMock(),\n            blockchainModuleManager: new BlockchainModuleManagerMock(),\n            logger: new Logger(),\n            config: {\n                maximumAssertionSizeInKb: 2500,\n            },\n        });\n    });\n\n    it('Validates assertion correctly', async () => {\n        let errorThrown = false;\n        try {\n            await validationService.validateAssertion(\n                '0xde58cc52a5ce3a04ae7a05a13176226447ac02489252e4d37a72cbe0aea46b42',\n                'hardhat',\n                {\n                    '@context': 'https://schema.org',\n                    '@id': 'https://tesla.modelX/2321',\n                    '@type': 'Car',\n                    name: 'Tesla Model X',\n                    brand: {\n                        '@type': 'Brand',\n                        name: 'Tesla',\n                    },\n                    model: 'Model X',\n                    manufacturer: {\n                        '@type': 'Organization',\n                        name: 'Tesla, Inc.',\n                    },\n                    fuelType: 'Electric',\n                },\n            );\n        } catch (error) {\n            errorThrown = true;\n        }\n        expect(errorThrown).to.be.false;\n    });\n\n    it('Tries to validate assertion but fails due to assertion size mismatch', async () => {\n        // todo after corrective component is implemented, update this logic\n        // let errorThrown = false;\n        // try {\n        //     await validationService.validateAssertion(\n        //         '0xde58cc52a5ce3a04ae7a05a13176226447ac02489252e4d37a72cbe0aea46b42',\n        //         'hardhat',\n        //         {\n        //             '@context': 'https://schema.org',\n        //             '@id': 'https://tesla.modelX/2321',\n        //             '@type': 'Car',\n        //             name: 'Tesla Model X',\n        //         },\n        //     );\n        // } catch (error) {\n        //     errorThrown = true;\n        // }\n        // expect(errorThrown).to.be.true;\n    });\n\n    it('Tries to validate assertion but fails due to triple number mismatch', async () => {\n        validationService.blockchainModuleManager.getAssertionTriplesNumber = (\n            blockchain,\n            assertionId,\n        ) => 5; // Will lead to mismatch with assertion calculated value\n\n        let errorThrown = false;\n        try {\n            await validationService.validateAssertion(\n                '0xde58cc52a5ce3a04ae7a05a13176226447ac02489252e4d37a72cbe0aea46b42',\n                'hardhat',\n                {\n                    '@context': 'https://schema.org',\n                    '@id': 'https://tesla.modelX/2321',\n                    '@type': 'Car',\n                    name: 'Tesla Model X',\n                    brand: {\n                        '@type': 'Brand',\n                        name: 'Tesla',\n                    },\n                    model: 'Model X',\n                    manufacturer: {\n                        '@type': 'Organization',\n                        name: 'Tesla, Inc.',\n                    },\n                    fuelType: 'Electric',\n                },\n            );\n        } catch (error) {\n            errorThrown = true;\n        }\n        expect(errorThrown).to.be.true;\n    });\n\n    it('Tries to validate assertion but fails due to chunk number mismatch', async () => {\n        validationService.blockchainModuleManager.getAssertionChunksNumber = (\n            blockchain,\n            assertionId,\n        ) => 5; // Will lead to mismatch with assertion chunk number calculated value\n\n        let errorThrown = false;\n        try {\n            await validationService.validateAssertion(\n                '0xde58cc52a5ce3a04ae7a05a13176226447ac02489252e4d37a72cbe0aea46b42',\n                'hardhat',\n                {\n                    '@context': 'https://schema.org',\n                    '@id': 'https://tesla.modelX/2321',\n                    '@type': 'Car',\n                    name: 'Tesla Model X',\n                    brand: {\n                        '@type': 'Brand',\n                        name: 'Tesla',\n                    },\n                    model: 'Model X',\n                    manufacturer: {\n                        '@type': 'Organization',\n                        name: 'Tesla, Inc.',\n                    },\n                    fuelType: 'Electric',\n                },\n            );\n        } catch (error) {\n            errorThrown = true;\n        }\n        expect(errorThrown).to.be.true;\n    });\n\n    it('Tries to validate assertion but fails due to validation manager returning wrong assertion id', async () => {\n        // todo after corrective component is implemented, update this logic\n        // Will lead to mismatch with passed assertion id\n        // validationService.validationModuleManager.calculateRoot = (assertion) => '';\n        //\n        // let errorThrown = false;\n        // try {\n        //     await validationService.validateAssertion(\n        //         '0xde58cc52a5ce3a04ae7a05a13176226447ac02489252e4d37a72cbe0aea46b42',\n        //         'hardhat',\n        //         {\n        //             '@context': 'https://schema.org',\n        //             '@id': 'https://tesla.modelX/2321',\n        //             '@type': 'Car',\n        //             name: 'Tesla Model X',\n        //             brand: {\n        //                 '@type': 'Brand',\n        //                 name: 'Tesla',\n        //             },\n        //             model: 'Model X',\n        //             manufacturer: {\n        //                 '@type': 'Organization',\n        //                 name: 'Tesla, Inc.',\n        //             },\n        //             fuelType: 'Electric',\n        //         },\n        //     );\n        // } catch (error) {\n        //     errorThrown = true;\n        // }\n        // expect(errorThrown).to.be.true;\n    });\n});\n"
  },
  {
    "path": "test/unit/sparlql-query-service.test.js",
    "content": "// /* eslint-disable */\n// import Blazegraph from '../../src/modules/triple-store/implementation/ot-blazegraph/ot-blazegraph.js';\n// import GraphDB from '../../src/modules/triple-store/implementation/ot-graphdb/ot-graphdb.js';\n// import Fuseki from '../../src/modules/triple-store/implementation/ot-fuseki/ot-fuseki.js';\n// const {\n//     describe,\n//     it,\n//     before,\n//     after,\n// } = require('mocha');\n// import chai from 'chai';\n\n// const {\n//     assert,\n//     expect,\n// } = chai;\n\n// import Logger from '../../src/logger/logger';\n// import fs from 'fs';\n\n// let sparqlService = null;\n// let logger = null;\n// chai.use(require('chai-as-promised'));\n\n// this.makeId = function (length) {\n//     let result = '';\n//     const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';\n//     const charactersLength = characters.length;\n//     for (let i = 0; i < length; i++) {\n//         result += characters.charAt(Math.floor(Math.random() *\n//             charactersLength));\n//     }\n//     return result;\n// };\n\n// describe('Sparql module', () => {\n//     before('Initialize Logger', async () => {\n//         logger = new Logger('trace', false);\n//     });\n//     before('Init Sparql Module', async () => {\n//         const configFile = JSON.parse(fs.readFileSync('.origintrail_noderc.tests'));\n//         const config = configFile.graphDatabase;\n//         assert.isNotNull(config.url);\n//         assert.isNotEmpty(config.url);\n//         assert.isNotNull(config.name);\n//         assert.isNotEmpty(config.name);\n//         switch (config.implementation) {\n//             case 'Blazegraph':\n//                 sparqlService = new Blazegraph(config);\n//                 break;\n//             case 'GraphDB':\n//                 sparqlService = new GraphDB(config);\n//                 break;\n//             case 'Fuseki':\n//                 sparqlService = new Fuseki(config);\n//                 break;\n//             default:\n//                 throw Error('Unknown graph database implementation')\n//         }\n//         await sparqlService.initialize(logger);\n//     });\n//     it('Check for cleanup', async () => {\n//         // Success\n//         expect(sparqlService.cleanEscapeCharacter('keywordabc'))\n//             .to\n//             .equal('keywordabc');\n//         // Fail\n//         expect(sparqlService.cleanEscapeCharacter('keywordabc\\''))\n//             .to\n//             .equal('keywordabc\\\\\\'');\n//     });\n//     it('Check limit creation', async () => {\n//         // Success\n//         expect(() => sparqlService.createLimitQuery({ limit: 'abc' }))\n//             .to\n//             .throw(Error);\n\n//         expect(() => sparqlService.createLimitQuery({ limit: Math.random() }))\n//             .to\n//             .throw(Error);\n\n//         expect(sparqlService.createLimitQuery({}))\n//             .to\n//             .equal('');\n//         // var randomnumber = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;\n//         // eslint-disable-next-line no-bitwise\n//         const random = (Math.random() * (99999999 - 1 + 1)) << 0;\n//         const negativeRandom = random * -1;\n\n//         expect(sparqlService.createLimitQuery({ limit: random }))\n//             .to\n//             .equal(`LIMIT ${random}`);\n\n//         expect(() => sparqlService.createLimitQuery({ limit: negativeRandom }))\n//             .to\n//             .throw(Error);\n//     });\n\n//     it('Check FindAssertionsByKeyword Errorhandling', async () => {\n//         await expect(sparqlService.findAssertionsByKeyword('abc', { limit: 'aaaa' }, false))\n//             .to\n//             .be\n//             .rejectedWith(Error);\n\n//         await expect(sparqlService.findAssertionsByKeyword('abc', { limit: '90' }, 'test'))\n//             .to\n//             .be\n//             .rejectedWith(Error);\n\n//         await expect(sparqlService.findAssertionsByKeyword('abc', {\n//             limit: '90',\n//             prefix: 'test',\n//         }))\n//             .to\n//             .be\n//             .rejectedWith(Error);\n//     });\n\n//     it('Check FindAssertionsByKeyword functionality', async () => {\n\n//         let id = this.makeId(65);\n//         const addTriple = await sparqlService.insert(`<did:dkg:${id}> schema:hasKeywords \"${id}\" `, `did:dkg:${id}`);\n//         // This can also be mocked if necessary\n//         const test = await sparqlService.findAssertionsByKeyword(id, {\n//             limit: 5,\n//             prefix: true,\n//         }, true);\n//         expect(test)\n//             .to\n//             .be\n//             .not\n//             .empty;\n\n//         const testTwo = await sparqlService.findAssertionsByKeyword(id, {\n//             limit: 5,\n//             prefix: false,\n//         }, true);\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(testTwo)\n//             .to\n//             .be\n//             .not\n//             .empty;\n//     })\n//         .timeout(600000);\n\n//     it('Check createFilterParameter', async () => {\n//         expect(sparqlService.createFilterParameter('', ''))\n//             .to\n//             .equal('');\n\n//         expect(sparqlService.createFilterParameter('\\'', ''))\n//             .to\n//             .equal('');\n\n//         expect(sparqlService.createFilterParameter('\\'', sparqlService.filtertype.KEYWORD))\n//             .to\n//             .equal('FILTER (lcase(?keyword) = \\'\\\\\\'\\')');\n\n//         expect(sparqlService.createFilterParameter('abcd', sparqlService.filtertype.KEYWORD))\n//             .to\n//             .equal('FILTER (lcase(?keyword) = \\'abcd\\')');\n\n//         expect(sparqlService.createFilterParameter('abcd', sparqlService.filtertype.KEYWORDPREFIX))\n//             .to\n//             .equal('FILTER contains(lcase(?keyword),\\'abcd\\')');\n\n//         expect(sparqlService.createFilterParameter('abcd', sparqlService.filtertype.TYPES))\n//             .to\n//             .equal('FILTER (?type IN (abcd))');\n\n//         expect(sparqlService.createFilterParameter('abcd', sparqlService.filtertype.ISSUERS))\n//             .to\n//             .equal('FILTER (?issuer IN (abcd))');\n//     });\n//     it('Check FindAssetsByKeyword functionality', async () => {\n//         //Add new entry, so we can check if we find it really\n\n//         let id = this.makeId(65);\n//         let triples = `\n//                                 <did:dkg:${id}> schema:hasKeywords \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasTimestamp \"2022-04-18T06:48:05.123Z\" .\n//                                 <did:dkg:${id}> schema:hasUALs \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasIssuer \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasType \"${id}\" .\n//                              `;\n\n//         const addTriple = await sparqlService.insert(triples, `did:dkg:${id}`);\n//         expect(addTriple)\n//             .to\n//             .be\n//             .true;\n\n//         const testContains = await sparqlService.findAssetsByKeyword(id.substring(1, 20), {\n//             limit: 5,\n//             prefix: true,\n//         }, true);\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(testContains)\n//             .to\n//             .be\n//             .not\n//             .empty;\n\n//         const testExact = await sparqlService.findAssetsByKeyword(id, {\n//             limit: 5,\n//             prefix: true,\n//         }, true);\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(testExact)\n//             .to\n//             .be\n//             .not\n//             .empty;\n//     })\n//         .timeout(600000);\n//     it('Check resolve functionality', async () => {\n//         // This can also be mocked if necessary\n//         const test = await sparqlService.resolve('0e62550721611b96321c7459e7790498240431025e46fce9cd99f2ea9763ffb0');\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(test)\n//             .to\n//             .be\n//             .not\n//             .null;\n//     })\n//         .timeout(600000);\n//     it('Check insert functionality', async () => {\n//         // This can also be mocked if necessary\n\n//         let id = this.makeId(65);\n//         const test = await sparqlService.insert(`<did:dkg:${id}> schema:hasKeywords \"${id}\" `, `did:dkg:${id}`);\n\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(test)\n//             .to\n//             .be\n//             .true;\n//     })\n//         .timeout(600000);\n\n//     it('Check assertions By Asset functionality', async () => {\n//         // This can also be mocked if necessary\n\n//         let id = this.makeId(65);\n//         let triples = `\n//                                 <did:dkg:${id}> schema:hasKeywords \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasTimestamp \"2022-04-18T06:48:05.123Z\" .\n//                                 <did:dkg:${id}> schema:hasUALs \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasIssuer \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasType \"${id}\" .\n//                              `;\n\n//         const addTriple = await sparqlService.insert(triples, `did:dkg:${id}`);\n//         expect(addTriple)\n//             .to\n//             .be\n//             .true;\n\n//         const testExact = await sparqlService.assertionsByAsset(id, {\n//             limit: 5,\n//             prefix: true,\n//         }, true);\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(testExact)\n//             .to\n//             .be\n//             .not\n//             .empty;\n\n//     })\n//         .timeout(600000);\n\n//     it('Check find Assertions functionality', async () => {\n//         // This can also be mocked if necessary\n\n//         let id = this.makeId(65);\n//         let triples = `\n//                                 <did:dkg:${id}> schema:hasKeywords \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasTimestamp \"2022-04-18T06:48:05.123Z\" .\n//                                 <did:dkg:${id}> schema:hasUALs \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasIssuer \"${id}\" .\n//                                 <did:dkg:${id}> schema:hasType \"${id}\" .\n//                              `;\n\n//         const addTriple = await sparqlService.insert(triples, `did:dkg:${id}`);\n//         expect(addTriple)\n//             .to\n//             .be\n//             .true;\n\n//         const testExact = await sparqlService.findAssertions(triples);\n//         // eslint-disable-next-line no-unused-expressions\n//         expect(testExact)\n//             .to\n//             .be\n//             .not\n//             .empty;\n\n//     })\n//         .timeout(600000);\n// });\n"
  },
  {
    "path": "test/utilities/dkg-client-helper.mjs",
    "content": "import DKG from 'dkg.js';\nimport { CONTENT_ASSET_HASH_FUNCTION_ID } from '../../src/constants/constants.js';\n\nclass DkgClientHelper {\n    constructor(config) {\n        this.client = new DKG(config);\n    }\n\n    async info() {\n        return this.client.node.info();\n    }\n\n    async publish(data, userOptions = {}) {\n        const defaultOptions = {\n            visibility: 'public',\n            epochsNum: 5,\n            hashFunctionId: CONTENT_ASSET_HASH_FUNCTION_ID,\n            minimumNumberOfNodeReplications: 1,\n            minimumNumberOfFinalizationConfirmations: 0,\n        };\n\n        const options = { ...defaultOptions, ...userOptions };\n\n        return this.client.asset.create(data, options);\n    }\n\n    async get(ual, state, userOptions = {}) {\n        const defaultOptions = {\n            state,\n            validate: true,\n        };\n\n        const options = { ...defaultOptions, ...userOptions };\n\n        return this.client.asset.get(ual, options);\n    }\n\n    async query(query) {\n        return this.client.query(query);\n    }\n}\n\nexport default DkgClientHelper;\n"
  },
  {
    "path": "test/utilities/http-api-helper.mjs",
    "content": "import { setTimeout } from 'timers/promises';\nimport axios from 'axios';\n\nconst TERMINAL_STATUSES = ['COMPLETED', 'FAILED'];\n\nclass HttpApiHelper {\n    async info(nodeRpcUrl) {\n        return this._sendRequest('get', `${nodeRpcUrl}/info`);\n    }\n\n    async get(nodeRpcUrl, requestBody) {\n        return this._sendRequest('post', `${nodeRpcUrl}/get`, requestBody);\n    }\n\n    async getOperationResult(nodeRpcUrl, operationName, operationId) {\n        return this._sendRequest('get', `${nodeRpcUrl}/${operationName}/${operationId}`);\n    }\n\n    async publish(nodeRpcUrl, requestBody) {\n        return this._sendRequest('post', `${nodeRpcUrl}/publish`, requestBody);\n    }\n\n    async update(nodeRpcUrl, requestBody) {\n        return this._sendRequest('post', `${nodeRpcUrl}/update`, requestBody);\n    }\n\n    /**\n     * Polls an operation until it reaches a terminal status (COMPLETED or FAILED).\n     * @param {string} nodeRpcUrl\n     * @param {string} operationName  e.g. 'publish', 'get', 'update'\n     * @param {string} operationId\n     * @param {object} [options]\n     * @param {number} [options.intervalMs=5000]  delay between retries\n     * @param {number} [options.maxRetries=5]\n     * @returns {Promise<object>} the final operation result response\n     */\n    async pollOperationResult(nodeRpcUrl, operationName, operationId, { intervalMs = 5000, maxRetries = 5 } = {}) {\n        for (let attempt = 0; attempt < maxRetries; attempt += 1) {\n            // eslint-disable-next-line no-await-in-loop\n            const result = await this.getOperationResult(nodeRpcUrl, operationName, operationId);\n            if (TERMINAL_STATUSES.includes(result.data.status)) {\n                return result;\n            }\n            if (attempt < maxRetries - 1) {\n                // eslint-disable-next-line no-await-in-loop\n                await setTimeout(intervalMs);\n            }\n        }\n        throw new Error(\n            `Operation ${operationName}/${operationId} did not reach a terminal status after ${maxRetries} attempts`,\n        );\n    }\n\n    async _sendRequest(method, url, data) {\n        return axios({\n            method,\n            url,\n            ...data && { data },\n        }).catch((error) => {\n            const errorWithStatus = new Error(error.message);\n            if (error.response) {\n                errorWithStatus.statusCode = error.response.status;\n            }\n            throw errorWithStatus;\n        });\n    }\n}\nexport default HttpApiHelper;\n"
  },
  {
    "path": "test/utilities/steps-utils.mjs",
    "content": "import { fork } from 'child_process';\n\nconst otNodeProcessPath = './test/bdd/steps/lib/ot-node-process.mjs';\n\n/**\n * Fixed libp2p private key for the bootstrap node.\n * Produces a deterministic PeerID (QmWyf3dtqJnhuCpzEDTNmNFYc5tjxTrXhGcUUmGHdg2gtj) that matches\n * the bootstrap peer address baked into the default network config so regular nodes can find it.\n */\nconst BOOTSTRAP_LIBP2P_PRIVATE_KEY =\n    'CAAS4QQwggJdAgEAAoGBALOYSCZsmINMpFdH8ydA9CL46fB08F3ELfb9qiIq+z4RhsFwi7lByysRnYT/NLm8jZ4RvlsSqOn2ZORJwBywYD5MCvU1TbEWGKxl5LriW85ZGepUwiTZJgZdDmoLIawkpSdmUOc1Fbnflhmj/XzAxlnl30yaa/YvKgnWtZI1/IwfAgMBAAECgYEAiZq2PWqbeI6ypIVmUr87z8f0Rt7yhIWZylMVllRkaGw5WeGHzQwSRQ+cJ5j6pw1HXMOvnEwxzAGT0C6J2fFx60C6R90TPos9W0zSU+XXLHA7AtazjlSnp6vHD+RxcoUhm1RUPeKU6OuUNcQVJu1ZOx6cAcP/I8cqL38JUOOS7XECQQDex9WUKtDnpHEHU/fl7SvCt0y2FbGgGdhq6k8nrWtBladP5SoRUFuQhCY8a20fszyiAIfxQrtpQw1iFPBpzoq1AkEAzl/s3XPGi5vFSNGLsLqbVKbvoW9RUaGN8o4rU9oZmPFL31Jo9FLA744YRer6dYE7jJMel7h9VVWsqa9oLGS8AwJALYwfv45Nbb6yGTRyr4Cg/MtrFKM00K3YEGvdSRhsoFkPfwc0ZZvPTKmoA5xXEC8eC2UeZhYlqOy7lL0BNjCzLQJBAMpvcgtwa8u6SvU5B0ueYIvTDLBQX3YxgOny5zFjeUR7PS+cyPMQ0cyql8jNzEzDLcSg85tkDx1L4wi31Pnm/j0CQFH/6MYn3r9benPm2bYSe9aoJp7y6ht2DmXmoveNbjlEbb8f7jAvYoTklJxmJCcrdbNx/iCj2BuAinPPgEmUzfQ=';\n\n// Port 9000 is PHP-FPM's default port and is commonly occupied on developer machines.\n// Use high-numbered ports that are unlikely to conflict with system services or retries.\nconst BOOTSTRAP_NETWORK_PORT = 19000;\nconst BOOTSTRAP_RPC_PORT = 18900;\n\n/**\n * Loopback multiaddr for the bootstrap node. Regular nodes dial this on startup for DHT seeding.\n * PeerID corresponds to BOOTSTRAP_LIBP2P_PRIVATE_KEY. Uses 127.0.0.1 — the default config uses\n * 0.0.0.0 which is not a valid dial address and was causing silent connection failures.\n */\nconst BOOTSTRAP_PEER_MULTIADDR = `/ip4/127.0.0.1/tcp/${BOOTSTRAP_NETWORK_PORT}/p2p/QmWyf3dtqJnhuCpzEDTNmNFYc5tjxTrXhGcUUmGHdg2gtj`;\n\nclass StepsUtils {\n    forkNode(nodeConfiguration) {\n        const forkedNode = fork(otNodeProcessPath, [], { silent: true });\n        forkedNode.send(JSON.stringify(nodeConfiguration));\n        return forkedNode;\n    }\n\n    /**\n     * Builds a full node configuration object for BDD test scenarios.\n     *\n     * @param {Array<{blockchainId: string, port: number, operationalWallet: object, managementWallet: object}>} blockchains\n     * @param {number} nodeIndex   - Zero-based index; drives unique DB names, ports, and triple-store repos\n     * @param {string} nodeName\n     * @param {number} rpcPort     - HTTP API port\n     * @param {number} networkPort - libp2p P2P port\n     * @param {boolean} [bootstrap=false] - When true, uses the fixed libp2p key (known PeerID),\n     *                                      empty bootstrap list, and isolated DB/data paths\n     * @param {string} [bootstrapPeerMultiaddr] - For regular nodes, the bootstrap peer multiaddr to dial.\n     *                                            If omitted, BOOTSTRAP_PEER_MULTIADDR is used.\n     * @returns {object} Node configuration\n     */\n    createNodeConfiguration(\n        blockchains,\n        nodeIndex,\n        nodeName,\n        rpcPort,\n        networkPort,\n        bootstrap = false,\n        bootstrapPeerMultiaddr = BOOTSTRAP_PEER_MULTIADDR,\n    ) {\n        let config = {\n            modules: {\n                blockchain: {\n                    implementation: {},\n                },\n                network: {\n                    implementation: {\n                        'libp2p-service': {\n                            config: {\n                                port: networkPort,\n                                privateKey: bootstrap ? BOOTSTRAP_LIBP2P_PRIVATE_KEY : undefined,\n                                bootstrap: bootstrap ? [] : [bootstrapPeerMultiaddr],\n                                peerRouting: {\n                                    refreshManager: {\n                                        enabled: false,\n                                    },\n                                },\n                            },\n                        },\n                    },\n                },\n                repository: {\n                    implementation: {\n                        'sequelize-repository': {\n                            config: {\n                                database: bootstrap\n                                    ? 'operationaldbbootstrap'\n                                    : `operationaldbnode${nodeIndex}`,\n                            },\n                        },\n                    },\n                },\n                tripleStore: {\n                    implementation: {\n                        'ot-blazegraph': {\n                            config: {\n                                repositories: {\n                                    dkg: {\n                                        url: 'http://localhost:9999',\n                                        name: bootstrap ? 'dkg-bootstrap' : `dkg-${nodeIndex}`,\n                                        username: 'admin',\n                                        password: '',\n                                    },\n                                    privateCurrent: {\n                                        url: 'http://localhost:9999',\n                                        name: bootstrap\n                                            ? 'private-current-bootstrap'\n                                            : `private-current-${nodeIndex}`,\n                                        username: 'admin',\n                                        password: '',\n                                    },\n                                    publicCurrent: {\n                                        url: 'http://localhost:9999',\n                                        name: bootstrap\n                                            ? 'public-current-bootstrap'\n                                            : `public-current-${nodeIndex}`,\n                                        username: 'admin',\n                                        password: '',\n                                    },\n                                },\n                            },\n                        },\n                    },\n                },\n                validation: {\n                    enabled: true,\n                    implementation: {\n                        'merkle-validation': {\n                            enabled: true,\n                            package: './validation/implementation/merkle-validation.js',\n                        },\n                    },\n                },\n                httpClient: {\n                    implementation: {\n                        'express-http-client': {\n                            config: {\n                                port: rpcPort,\n                            },\n                        },\n                    },\n                },\n            },\n            auth: {\n                ipBasedAuthEnabled: false,\n            },\n            operationalDatabase: {\n                databaseName: bootstrap\n                    ? 'operationaldbbootstrap'\n                    : `operationaldbnode${nodeIndex}`,\n            },\n            rpcPort,\n            appDataPath: bootstrap ? 'test-data-bootstrap' : `test-data${nodeIndex}`,\n            graphDatabase: {\n                name: nodeName,\n            },\n        };\n\n        for (const blockchain of blockchains) {\n            config.modules.blockchain.implementation[blockchain.blockchainId] = {\n                enabled: true,\n                package: './blockchain/implementation/hardhat/hardhat-service.js',\n                config: {\n                    hubContractAddress: '0x5FbDB2315678afecb367f032d93F642f64180aa3',\n                    rpcEndpoints: [`http://localhost:${blockchain.port}`],\n                    initialStakeAmount: 50000,\n                    initialAskAmount: 0.2,\n                    operationalWallets: [{\n                        privateKey: blockchain.operationalWallet.privateKey,\n                        evmAddress: blockchain.operationalWallet.address,\n                    }],\n                    evmManagementWalletPublicKey: blockchain.managementWallet.address,\n                    evmManagementWalletPrivateKey: blockchain.managementWallet.privateKey,\n                    nodeName: bootstrap ? 'bootstrap' : `node${nodeIndex}`,\n                },\n            };\n        }\n        return config;\n    }\n}\nexport { BOOTSTRAP_NETWORK_PORT, BOOTSTRAP_RPC_PORT, BOOTSTRAP_PEER_MULTIADDR };\nexport default StepsUtils;\n"
  },
  {
    "path": "test/utilities/utilities.js",
    "content": "import TripleStoreModuleManager from '../../src/modules/triple-store/triple-store-module-manager.js';\n\nclass Utilities {\n    static unpackRawTableToArray(rawTable) {\n        return rawTable.rawTable[0];\n    }\n\n    /**\n     * Unpacks cucumber dictionary into simple dictionary\n     * @param rawTable\n     */\n    static unpackRawTable(rawTable) {\n        const parse = (val) => {\n            if (!Number.isNaN(Number(val))) {\n                return Number(val);\n            }\n\n            if (val.toLowerCase() === 'true' || val.toLowerCase() === 'false') {\n                return Boolean(val);\n            }\n\n            return val;\n        };\n\n        const unpacked = {};\n        if (rawTable) {\n            for (const row of rawTable.rawTable) {\n                let value;\n                if (row.length > 2) {\n                    value = [];\n                    for (let index = 1; index < row.length; index += 1) {\n                        if (!row[index] != null && row[index] !== '') {\n                            value.push(parse(row[index]));\n                        }\n                    }\n                } else {\n                    value = parse(row[1]);\n                }\n\n                const keyParts = row[0].split('.');\n                if (keyParts.length === 1) {\n                    unpacked[keyParts[0]] = value;\n                } else {\n                    let current = unpacked;\n                    for (let j = 0; j < keyParts.length - 1; j += 1) {\n                        if (!current[keyParts[j]]) {\n                            current[keyParts[j]] = {};\n                        }\n                        current = current[keyParts[j]];\n                    }\n                    current[keyParts[keyParts.length - 1]] = value;\n                }\n            }\n        }\n        return unpacked;\n    }\n\n    static async deleteTripleStoreRepositories(config, logger) {\n        const tripleStoreModuleManager = new TripleStoreModuleManager({ config, logger });\n        await tripleStoreModuleManager.initialize();\n\n        for (const implementationName of tripleStoreModuleManager.getImplementationNames()) {\n            // eslint-disable-next-line no-shadow\n            const { module, config } =\n                tripleStoreModuleManager.getImplementation(implementationName);\n            // eslint-disable-next-line no-await-in-loop\n            await Promise.all(\n                Object.keys(config.repositories).map((repository) =>\n                    module.deleteRepository(repository),\n                ),\n            );\n        }\n    }\n}\n\nexport default Utilities;\n"
  },
  {
    "path": "tools/local-network-setup/.origintrail_noderc_template.json",
    "content": "{\n    \"logLevel\": \"trace\",\n    \"modules\": {\n        \"httpClient\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"express-http-client\": {\n                    \"package\": \"./http-client/implementation/express-http-client.js\",\n                    \"config\": {}\n                }\n            }\n        },\n        \"repository\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"sequelize-repository\": {\n                    \"package\": \"./repository/implementation/sequelize/sequelize-repository.js\",\n                    \"config\": {}\n                }\n            }\n        },\n        \"tripleStore\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"ot-blazegraph\": {\n                    \"enabled\": false,\n                    \"package\": \"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js\",\n                    \"config\": {\n                        \"repositories\": {\n                            \"dkg\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"dkg\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            },\n                            \"privateCurrent\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"private-current\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            },\n                            \"publicCurrent\": {\n                                \"url\": \"http://localhost:9999\",\n                                \"name\": \"public-current\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            }\n                        }\n                    }\n                },\n                \"ot-fuseki\": {\n                    \"enabled\": false,\n                    \"package\": \"./triple-store/implementation/ot-fuseki/ot-fuseki.js\",\n                    \"config\": {\n                        \"repositories\": {\n                            \"dkg\": {\n                                \"url\": \"http://localhost:3030\",\n                                \"name\": \"dkg\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            }\n                        }\n                    }\n                },\n                \"ot-graphdb\": {\n                    \"enabled\": false,\n                    \"package\": \"./triple-store/implementation/ot-graphdb/ot-graphdb.js\",\n                    \"config\": {\n                        \"repositories\": {\n                            \"dkg\": {\n                                \"url\": \"http://localhost:7200\",\n                                \"name\": \"dkg\",\n                                \"username\": \"admin\",\n                                \"password\": \"\"\n                            }\n                        }\n                    }\n                }\n            }\n        },\n        \"network\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"libp2p-service\": {\n                    \"package\": \"./network/implementation/libp2p-service.js\",\n                    \"config\": {\n                        \"port\": 9001,\n                        \"bootstrap\": [\n                            \"/ip4/0.0.0.0/tcp/9100/p2p/QmWyf3dtqJnhuCpzEDTNmNFYc5tjxTrXhGcUUmGHdg2gtj\"\n                        ]\n                    }\n                }\n            }\n        },\n        \"blockchain\": {\n            \"implementation\": {\n                \"hardhat1:31337\": {\n                    \"enabled\": true,\n                    \"package\": \"./blockchain/implementation/hardhat/hardhat-service.js\",\n                    \"config\": {\n                        \"operationalWallets\": [\n                            {\n                                \"evmAddress\": \"0xd6879C0A03aDD8cFc43825A42a3F3CF44DB7D2b9\",\n                                \"privateKey\": \"0x02b39cac1532bef9dba3e36ec32d3de1e9a88f1dda597d3ac6e2130aed9adc4e\"\n                            }\n                        ],\n                        \"rpcEndpoints\": []\n                    }\n                },\n                \"hardhat2:31337\": {\n                    \"package\": \"./blockchain/implementation/hardhat/hardhat-service.js\",\n                    \"config\": {\n                        \"operationalWallets\": [\n                            {\n                                \"evmAddress\": \"0xd6879C0A03aDD8cFc43825A42a3F3CF44DB7D2b9\",\n                                \"privateKey\": \"0x02b39cac1532bef9dba3e36ec32d3de1e9a88f1dda597d3ac6e2130aed9adc4e\"\n                            }\n                        ],\n                        \"rpcEndpoints\": []\n                    }\n                }\n            }\n        },\n        \"blockchainEvents\": {\n            \"enabled\": true,\n            \"implementation\": {\n                \"ot-ethers\": {\n                    \"enabled\": true,\n                    \"package\": \"./blockchain-events/implementation/ot-ethers/ot-ethers.js\",\n                    \"config\": {\n                        \"blockchains\": [\"hardhat1:31337\", \"hardhat2:31337\"],\n                        \"rpcEndpoints\": {\n                            \"hardhat1:31337\": [\"http://localhost:8545\"],\n                            \"hardhat2:31337\": [\"http://localhost:9545\"]\n                        },\n                        \"hubContractAddress\": {\n                            \"hardhat1:31337\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\",\n                            \"hardhat2:31337\": \"0x5FbDB2315678afecb367f032d93F642f64180aa3\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"auth\": {\n        \"ipWhitelist\": [\"::1\", \"127.0.0.1\"]\n    }\n}\n"
  },
  {
    "path": "tools/local-network-setup/README.md",
    "content": "# DKG local network setup tool\n\nThis tool will help you set up a local DKG V8 network running with the Hardhat blockchain. It is useful for development and testing purposes and is used internally by the OriginTrail core developers.\n<br/>\n\n**Note: This tool is an internal tool used by the OriginTrail team and thus is developed for our workflow, meaning that it currently only supports MacOS and Linux**, but we encourage you to adapt it for your workflow as well.\n\n# Prerequisites\n\n-   An installed and running triplestore (graph database)\n    -   We recommend testing with Blazegraph. In order to download Blazegraph, please visit their official [website](https://blazegraph.com/). Alternatively other triple stores can be used (GraphBD or and other RDF native graph databases)\n-   An installed and running MySQL server\n-   You should have installed npm and Node.js (v20)\n\n# Setup instructions\n\nIn order to run the local network you fist need to clone the \"dkg-engine\" repository.\n<br/>\n\n## 1. CLONE DKG-ENGINE REPOSITORY & INSTALL DEPENDENCIES\n\nAfter cloning the **dkg-engine** repository, please checkout to \"v8/develop\" branch and install dependencies by running:\n\n```bash\ngit clone https://github.com/OriginTrail/dkg-engine.git && cd dkg-engine/ && npm install && cd ..\n```\n\n<br/>\n\n### 2.2 Create the .env file inside the \"dkg-engine\" directory:\n\n```bash\nnano .env\n```\n\nand paste the following content inside (save and close):\n\n```bash\nNODE_ENV=development\nRPC_ENDPOINT_BC1=http://localhost:8545\nRPC_ENDPOINT_BC2=http://localhost:9545\n```\n\n**Note:** The private key above is used ONLY for convenience and SHOULD be changed to a secure key when used in production.\n<br/>\n\n## 3. START THE LOCAL NETWORK\n\n## Specifying the number of nodes\n\nYou can specify to run anywhere between one and twenty nodes with the `--nodes` parameter.\n\n**Note:** All nodes assume MySQL username root and no password. To change the MySQL login information update the .origintrail_noderc template file sequelize-repository config object with your username and password<br/>\n\nThe first node will be named `bootstrap`, while subsequent nodes will be named `dh1, dh2, ...`. <br/>\n\n### MacOS\n\n```bash\nbash ./tools/local-network-setup/setup-macos-environment.sh --nodes=12\n```\n\n### Linux\n\n```bash\n./tools/local-network-setup/setup-linux-environment.sh --nodes=12\n```\n\n**Note:** With the above commands, we will start two hardhat instances, deploy contracts, deploy a 12 node network (1 bootstrap and 11 subsequent nodes)<br/>\n\n## Specifying the blockchain network\n\nYou can specify the blockchain network you want to connect to with `--network` parameter.\nAvailable networks:\n\n-   hardhat - default network\n\n# Contribution\n\nOriginTrail is an open source project. We happily invite you to join us in our mission of building decentralized knowledge graph - we're excited for your contributions! Join us in discord to meet the dev community\n\n### Useful links\n\n[OriginTrail website](https://origintrail.io)\n\n[OriginTrail documentation page](http://docs.origintrail.io)\n\n[OriginTrail Discord Group](https://discordapp.com/invite/FCgYk2S)\n\n[OriginTrail Telegram Group](https://t.me/origintrail)\n\n[OriginTrail Twitter](https://twitter.com/origin_trail)\n"
  },
  {
    "path": "tools/local-network-setup/generate-config-files.js",
    "content": "/* eslint-disable */\nimport 'dotenv/config';\nimport mysql from 'mysql2';\nimport path from 'path';\nimport fs from 'fs-extra';\nimport TripleStoreModuleManager from '../../src/modules/triple-store/triple-store-module-manager.js';\nimport Logger from '../../src/logger/logger.js';\nimport { unlink } from 'fs/promises';\n\nconst { readFile, writeFile, stat } = fs;\n\nconst generalConfig = JSON.parse(await readFile('./config/config.json'));\nconst templatePath = path.join('./tools/local-network-setup/.origintrail_noderc_template.json');\n\nconst privateKeysFile = await readFile('test/bdd/steps/api/datasets/privateKeys.json');\nconst publicKeysFile = await readFile('test/bdd/steps/api/datasets/publicKeys.json');\nconst privateKeys = JSON.parse(privateKeysFile.toString());\nconst publicKeys = JSON.parse(publicKeysFile.toString());\n// todo update this logic\nconst privateKeysManagementWalletFile = await readFile(\n    'test/bdd/steps/api/datasets/privateKeys-management-wallets.json',\n);\nconst publicKeysManagementWalletFile = await readFile(\n    'test/bdd/steps/api/datasets/publicKeys-management-wallets.json',\n);\nconst privateKeysManagementWallet = JSON.parse(privateKeysManagementWalletFile.toString());\nconst publicKeysManagementWallet = JSON.parse(publicKeysManagementWalletFile.toString());\n\nconst logger = new Logger(generalConfig.development.logLevel);\n\nconst numberOfNodes = parseInt(process.argv[2], 10);\nconst blockchain = process.argv[3];\nconst tripleStoreImplementation = process.argv[4];\nconst hubContractAddress = process.argv[5];\nconst libp2pBootstrapPrivateKey =\n    'CAAS4QQwggJdAgEAAoGBALOYSCZsmINMpFdH8ydA9CL46fB08F3ELfb9qiIq+z4RhsFwi7lByysRnYT/NLm8jZ4RvlsSqOn2ZORJwBywYD5MCvU1TbEWGKxl5LriW85ZGepUwiTZJgZdDmoLIawkpSdmUOc1Fbnflhmj/XzAxlnl30yaa/YvKgnWtZI1/IwfAgMBAAECgYEAiZq2PWqbeI6ypIVmUr87z8f0Rt7yhIWZylMVllRkaGw5WeGHzQwSRQ+cJ5j6pw1HXMOvnEwxzAGT0C6J2fFx60C6R90TPos9W0zSU+XXLHA7AtazjlSnp6vHD+RxcoUhm1RUPeKU6OuUNcQVJu1ZOx6cAcP/I8cqL38JUOOS7XECQQDex9WUKtDnpHEHU/fl7SvCt0y2FbGgGdhq6k8nrWtBladP5SoRUFuQhCY8a20fszyiAIfxQrtpQw1iFPBpzoq1AkEAzl/s3XPGi5vFSNGLsLqbVKbvoW9RUaGN8o4rU9oZmPFL31Jo9FLA744YRer6dYE7jJMel7h9VVWsqa9oLGS8AwJALYwfv45Nbb6yGTRyr4Cg/MtrFKM00K3YEGvdSRhsoFkPfwc0ZZvPTKmoA5xXEC8eC2UeZhYlqOy7lL0BNjCzLQJBAMpvcgtwa8u6SvU5B0ueYIvTDLBQX3YxgOny5zFjeUR7PS+cyPMQ0cyql8jNzEzDLcSg85tkDx1L4wi31Pnm/j0CQFH/6MYn3r9benPm2bYSe9aoJp7y6ht2DmXmoveNbjlEbb8f7jAvYoTklJxmJCcrdbNx/iCj2BuAinPPgEmUzfQ=';\n\nlogger.info(`Generating config for ${numberOfNodes} node(s)`);\n\n/********************************\n    CONFIG GENERATION\n*********************************/\n\nconst promises = [];\nfor (let i = 0; i < numberOfNodes; i += 1) {\n    promises.push(generateNodeConfig(i));\n}\nawait Promise.all(promises);\n\n/********************************\n    FUNCTIONS DEFINITIONS\n*********************************/\n\nasync function generateNodeConfig(nodeIndex) {\n    const configPath = path.join(\n        `./tools/local-network-setup/.node${nodeIndex}_origintrail_noderc.json`,\n    );\n    if (await fileExists(configPath)) {\n        await removeFile(configPath);\n    }\n\n    const template = JSON.parse(await readFile(templatePath));\n\n    logger.info(`Configuring node ${nodeIndex}`);\n    template.modules.tripleStore = generateTripleStoreConfig(\n        template.modules.tripleStore,\n        nodeIndex,\n    );\n    template.modules.blockchain = generateBlockchainConfig(template.modules.blockchain, nodeIndex);\n    template.modules.httpClient = generateHttpClientConfig(template.modules.httpClient, nodeIndex);\n    template.modules.network = generateNetworkConfig(template.modules.network, nodeIndex);\n    template.modules.repository = generateRepositoryConfig(template.modules.repository, nodeIndex);\n    template.appDataPath = `data${nodeIndex}`;\n    template.logLevel = process.env.LOG_LEVEL ?? template.logLevel;\n\n    await writeFile(configPath, JSON.stringify(template, null, 4));\n\n    const config = JSON.parse(await readFile(configPath));\n    await Promise.all([\n        dropDatabase(\n            `operationaldb${nodeIndex}`,\n            generalConfig.development.modules.repository.implementation['sequelize-repository']\n                .config,\n        ),\n        deleteTripleStoreRepositories(config),\n        deleteDataFolder(config),\n    ]);\n}\n\nfunction generateTripleStoreConfig(templateTripleStoreConfig, nodeIndex) {\n    const tripleStoreConfig = JSON.parse(JSON.stringify(templateTripleStoreConfig));\n\n    for (const implementationName in tripleStoreConfig.implementation) {\n        for (const [repository, config] of Object.entries(\n            tripleStoreConfig.implementation[implementationName].config.repositories,\n        )) {\n            tripleStoreConfig.implementation[implementationName].config.repositories[\n                repository\n            ].name = `${config.name}-${nodeIndex}`;\n        }\n        tripleStoreConfig.implementation[implementationName].enabled =\n            implementationName === tripleStoreImplementation ? true : false;\n    }\n\n    return tripleStoreConfig;\n}\n\nfunction generateBlockchainConfig(templateBlockchainConfig, nodeIndex) {\n    const blockchainConfig = JSON.parse(JSON.stringify(templateBlockchainConfig));\n    // console.log('************************');\n    // console.log(publicKeys[nodeIndex]);\n    // console.log('************************');\n    blockchainConfig.implementation['hardhat1:31337'].config = {\n        ...blockchainConfig.implementation['hardhat1:31337'].config,\n        hubContractAddress,\n        rpcEndpoints: [process.env.RPC_ENDPOINT_BC1],\n        operationalWallets: [\n            {\n                evmAddress: publicKeys[nodeIndex + 1],\n                privateKey: privateKeys[nodeIndex + 1],\n            },\n        ],\n        evmManagementWalletPublicKey: publicKeysManagementWallet[nodeIndex + 1],\n        evmManagementWalletPrivateKey: privateKeysManagementWallet[nodeIndex + 1],\n        nodeName: `LocalNode${nodeIndex + 1}`,\n    };\n\n    // TODO: Don't use string\n    blockchainConfig.implementation['hardhat2:31337'].config = {\n        ...blockchainConfig.implementation['hardhat2:31337'].config,\n        hubContractAddress,\n        rpcEndpoints: [process.env.RPC_ENDPOINT_BC2],\n        operationalWallets: [\n            {\n                evmAddress: publicKeys[nodeIndex + 1],\n                privateKey: privateKeys[nodeIndex + 1],\n            },\n        ],\n        evmManagementWalletPublicKey: publicKeysManagementWallet[nodeIndex + 1],\n        evmManagementWalletPrivateKey: privateKeysManagementWallet[nodeIndex + 1],\n        sharesTokenName: `LocalNode${nodeIndex + 1}`,\n        sharesTokenSymbol: `LN${nodeIndex + 1}`,\n    };\n\n    // Used for testing, add a few more wallets to later nodes\n    if (nodeIndex == 3) {\n        blockchainConfig.implementation['hardhat1:31337'].config.operationalWallets.push({\n            evmAddress: publicKeys[publicKeys.length - 1 - 1],\n            privateKey: privateKeys[privateKeys.length - 1 - 1],\n        });\n\n        blockchainConfig.implementation['hardhat2:31337'].config.operationalWallets.push({\n            evmAddress: publicKeys[publicKeys.length - 1 - 2],\n            privateKey: privateKeys[privateKeys.length - 1 - 2],\n        });\n    }\n\n    if (nodeIndex == 4) {\n        blockchainConfig.implementation['hardhat1:31337'].config.operationalWallets.push({\n            evmAddress: publicKeys[publicKeys.length - 1 - 3],\n            privateKey: privateKeys[privateKeys.length - 1 - 3],\n        });\n\n        blockchainConfig.implementation['hardhat2:31337'].config.operationalWallets.push({\n            evmAddress: publicKeys[publicKeys.length - 1 - 4],\n            privateKey: privateKeys[privateKeys.length - 1 - 4],\n        });\n    }\n\n    return blockchainConfig;\n}\n\nfunction generateHttpClientConfig(templateHttpClientConfig, nodeIndex) {\n    const httpClientConfig = JSON.parse(JSON.stringify(templateHttpClientConfig));\n\n    httpClientConfig.implementation['express-http-client'].config.port = 8900 + nodeIndex;\n\n    return httpClientConfig;\n}\n\nfunction generateNetworkConfig(templateNetworkConfig, nodeIndex) {\n    const networkConfig = JSON.parse(JSON.stringify(templateNetworkConfig));\n\n    networkConfig.implementation['libp2p-service'].config.port = 9100 + nodeIndex;\n    if (nodeIndex == 0) {\n        networkConfig.implementation['libp2p-service'].config.privateKey =\n            libp2pBootstrapPrivateKey;\n    }\n\n    return networkConfig;\n}\n\nfunction generateRepositoryConfig(templateRepositoryConfig, nodeIndex) {\n    const repositoryConfig = JSON.parse(JSON.stringify(templateRepositoryConfig));\n\n    repositoryConfig.implementation[\n        'sequelize-repository'\n    ].config.database = `operationaldb${nodeIndex}`;\n\n    return repositoryConfig;\n}\n\nasync function dropDatabase(name, config) {\n    logger.info(`Dropping database: ${name}`);\n    const password = process.env.REPOSITORY_PASSWORD ?? config.password;\n    const connection = mysql.createConnection({\n        database: name,\n        user: config.user,\n        host: config.host,\n        password,\n    });\n    try {\n        await connection.promise().query(`DROP DATABASE IF EXISTS ${name};`);\n    } catch (e) {\n        logger.warn(`Error while dropping database. Error: ${e.message}`);\n    }\n    connection.destroy();\n}\n\nasync function deleteTripleStoreRepositories(config) {\n    const tripleStoreModuleManager = new TripleStoreModuleManager({ config, logger });\n    await tripleStoreModuleManager.initialize();\n\n    for (const implementationName of tripleStoreModuleManager.getImplementationNames()) {\n        const { module, config } = tripleStoreModuleManager.getImplementation(implementationName);\n        await Promise.all(\n            Object.keys(config.repositories).map((repository) =>\n                module.deleteRepository(repository),\n            ),\n        );\n    }\n}\n\nasync function fileExists(filePath) {\n    try {\n        await stat(filePath);\n        return true;\n    } catch (e) {\n        return false;\n    }\n}\n\nasync function removeFile(filePath) {\n    await unlink(filePath);\n}\n\nasync function deleteDataFolder(config) {\n    if (await fileExists(config.appDataPath)) {\n        logger.trace(`Removing file on path: ${config.appDataPath}`);\n        await fs.rm(config.appDataPath, { recursive: true, force: true });\n    }\n}\n"
  },
  {
    "path": "tools/local-network-setup/run-local-blockchain.js",
    "content": "import LocalBlockchain from '../../test/bdd/steps/lib/local-blockchain.mjs';\n\nconst port = parseInt(process.argv[2], 10);\nconst version = process.argv.length > 3 ? process.argv[3] : '';\nconst localBlockchain = new LocalBlockchain();\n\nawait localBlockchain.initialize(port, console, version);\n"
  },
  {
    "path": "tools/local-network-setup/setup-linux-environment.sh",
    "content": "#!/bin/bash\npathToOtNode=$(pwd)\nnumberOfNodes=12\nnetwork=\"hardhat1:31337\"\ntripleStore=\"ot-blazegraph\"\navailableNetworks=(\"hardhat1:31337\")\nexport $(xargs < $pathToOtNode/.env)\nexport ACCESS_KEY=$RPC_ENDPOINT\n# Check for script arguments\nwhile [ $# -gt 0 ]; do\n  case \"$1\" in\n  \t# Override number of nodes if the argument is specified\n    --nodes=*)\n      numberOfNodes=\"${1#*=}\"\n      if [[ $numberOfNodes -le 0 ]]\n      then\n        echo Cannot run 0 nodes\n        exit 1\n      fi\n      ;;\n    # Print script usage if --help is given\n    --help)\n      echo \"Use --nodes=<insert_number_here> to specify the number of nodes to generate\"\n      echo \"Use --network=<insert_network_name> to specify the network to connect to. Available networks: hardhat.\"\n      exit 0\n      ;;\n    --network=*)\n      network=\"${1#*=}\"\n      if [[ ! \" ${availableNetworks[@]} \" =~ \" ${network} \" ]]\n      then\n          echo Invalid network parameter. Available networks: hardhat\n          exit 1\n      fi\n      ;;\n    --tripleStore=*)\n      tripleStore=\"${1#*=}\"\n      ;;\n    *)\n      printf \"***************************\\n\"\n      printf \"* Error: Invalid argument.*\\n\"\n      printf \"***************************\\n\"\n      exit 1\n  esac\n  shift\ndone\nif [[ $network == hardhat1:31337 ]]\nthen\n  echo ================================\n  echo ====== Starting hardhat1 ======\n  echo ================================\n  sh -c \"cd $pathToOtNode && node tools/local-network-setup/run-local-blockchain.js 8545 \" &\n  echo Waiting for hardhat to start and contracts deployment\n\n  while ! nc -z localhost 8545; do\n     sleep 1\n  done\n\n  echo Hardhat started.\n\n  echo ================================\n  echo ====== Starting hardhat 2 ======\n  echo ================================\n  sh -c \"cd $pathToOtNode && node tools/local-network-setup/run-local-blockchain.js 9545 \" &\n  echo Waiting for hardhat to start and contracts deployment\n\n  while ! nc -z localhost 9545; do\n     sleep 1\n  done\n\n  echo Hardhat started.\nfi\n\necho ================================\necho ====== Generating configs ======\necho ================================\n\nnode $pathToOtNode/tools/local-network-setup/generate-config-files.js $numberOfNodes $network $tripleStore $hubContractAddress\nsleep 5\necho ================================\necho ======== Starting nodes ========\necho ================================\n\nstartNode() {\n  echo Starting node $1\n  sh -c \"cd $pathToOtNode && node index.js ./tools/local-network-setup/.node$1_origintrail_noderc.json\" &\n}\n\ni=0\nwhile [[ $i -lt $numberOfNodes ]]\ndo\n  startNode $i\n  ((i = i + 1))\ndone\n\nwait\n# Close started background processes when done (https://stackoverflow.com/a/2173421)\ntrap \"trap - SIGTERM && kill -- -$$\" SIGINT SIGTERM EXIT\n"
  },
  {
    "path": "tools/local-network-setup/setup-macos-environment.sh",
    "content": "#!/bin/bash\n# Source bash profile to ensure PATH includes Homebrew\nsource ~/.bash_profile 2>/dev/null || true\npathToOtNode=$(pwd)\nnumberOfNodes=12\nnetwork=\"hardhat1:31337\"\ntripleStore=\"ot-blazegraph\"\navailableNetworks=(\"hardhat1:31337\")\nexport $(xargs < $pathToOtNode/.env)\nexport ACCESS_KEY=$RPC_ENDPOINT\n# Check for script arguments\nwhile [ $# -gt 0 ]; do\n  case \"$1\" in\n  \t# Override number of nodes if the argument is specified\n    --nodes=*)\n      numberOfNodes=\"${1#*=}\"\n      if [[ $numberOfNodes -le 0 ]]\n      then\n        echo Cannot run 0 nodes\n        exit 1\n      fi\n      ;;\n    # Print script usage if --help is given\n    --help)\n      echo \"Use --nodes=<insert_number_here> to specify the number of nodes to generate\"\n      echo \"Use --network=<insert_network_name> to specify the network to connect to. Available networks: hardhat, rinkeby. Default: hardhat\"\n      exit 0\n      ;;\n    --network=*)\n      network=\"${1#*=}\"\n      if [[ ! \" ${availableNetworks[@]} \" =~ \" ${network} \" ]]\n      then\n          echo Invalid network parameter. Available networks: hardhat\n          exit 1\n      fi\n      ;;\n    --tripleStore=*)\n      tripleStore=\"${1#*=}\"\n      ;;\n    *)\n      printf \"***************************\\n\"\n      printf \"* Error: Invalid argument.*\\n\"\n      printf \"***************************\\n\"\n      exit 1\n  esac\n  shift\ndone\nif [[ $network == hardhat1:31337 ]]\nthen\n  echo ================================\n  echo ====== Starting hardhat1 ======\n  echo ================================\n\n  osascript -e \"tell app \\\"Terminal\\\"\n        do script \\\"cd $pathToOtNode\n        node tools/local-network-setup/run-local-blockchain.js 8545 \\\"\n    end tell\"\n  echo Waiting for hardhat to start and contracts deployment\n\n  while ! nc -z localhost 8545; do\n     sleep 1\n  done\n\n  echo Hardhat started.\n\n  echo ================================\n  echo ====== Starting hardhat 2 ======\n  echo ================================\n\n  osascript -e \"tell app \\\"Terminal\\\"\n        do script \\\"cd $pathToOtNode\n        node tools/local-network-setup/run-local-blockchain.js 9545 \\\"\n    end tell\"\n  echo Waiting for hardhat to start and contracts deployment\n\n  while ! nc -z localhost 9545; do\n     sleep 1\n  done\n\n  echo Hardhat started.\nfi\n\necho ================================\necho ====== Generating configs ======\necho ================================\n\nnode $pathToOtNode/tools/local-network-setup/generate-config-files.js $numberOfNodes $network $tripleStore $hubContractAddress\nsleep 30\n\necho ================================\necho ====== Clearing Redis ==========\necho ================================\n\n# Clear all Redis data if redis-cli is available\nif command -v redis-cli &> /dev/null; then\n    redis-cli FLUSHALL\n    echo Redis cleared.\nelif [ -f \"/opt/homebrew/bin/redis-cli\" ]; then\n    /opt/homebrew/bin/redis-cli FLUSHALL\n    echo Redis cleared.\nelse\n    echo \"redis-cli not found. Skipping Redis cleanup.\"\n    echo \"To install Redis on macOS, run: brew install redis\"\nfi\n\necho ================================\necho ======== Starting nodes ========\necho ================================\n\nstartNode() {\n  echo Starting node $1\n  osascript -e \"tell app \\\"Terminal\\\"\n      do script \\\"cd $pathToOtNode\n  node index.js ./tools/local-network-setup/.node$1_origintrail_noderc.json\\\"\n  end tell\"\n}\n\ni=0\nwhile [[ $i -lt $numberOfNodes ]]\ndo\n  startNode $i\n  ((i = i + 1))\ndone\n"
  },
  {
    "path": "tools/ot-parachain-account-mapping/create-account-mapping-signature.js",
    "content": "/* eslint-disable import/no-extraneous-dependencies */\n/* eslint-disable no-console */\nimport { Wallet } from '@ethersproject/wallet';\nimport { joinSignature } from '@ethersproject/bytes';\nimport { _TypedDataEncoder } from '@ethersproject/hash';\nimport { u8aToHex } from '@polkadot/util';\nimport { decodeAddress } from '@polkadot/util-crypto';\n\nif (!process.argv[2]) {\n    console.log('Missing argument PRIVATE_ETH_KEY');\n    console.log(\n        'Usage: npm run create-account-mapping-signature PRIVATE_ETH_KEY SUBSTRATE_PUBLIC_KEY',\n    );\n    process.exit(1);\n}\n\nif (!process.argv[3]) {\n    console.log('Missing argument SUBSTRATE_PUBLIC_KEY');\n    console.log(\n        'Usage: npm run create-account-mapping-signature PRIVATE_ETH_KEY SUBSTRATE_PUBLIC_KEY',\n    );\n    process.exit(1);\n}\n\nconst PRIVATE_ETH_KEY = process.argv[2];\nconst PUBLIC_SUBSTRATE_ADDRESS = process.argv[3];\nconst HEX_SUBSTRATE_ADDRESS = u8aToHex(decodeAddress(PUBLIC_SUBSTRATE_ADDRESS));\n// Usage\n// node create-signature.js private_eth_key(with 0x) substrate_public_key\n\nasync function sign() {\n    const payload = {\n        types: {\n            EIP712Domain: [\n                {\n                    name: 'name',\n                    type: 'string',\n                },\n                {\n                    name: 'version',\n                    type: 'string',\n                },\n                {\n                    name: 'chainId',\n                    type: 'uint256',\n                },\n                {\n                    name: 'salt',\n                    type: 'bytes32',\n                },\n            ],\n            Transaction: [\n                {\n                    name: 'substrateAddress',\n                    type: 'bytes',\n                },\n            ],\n        },\n        primaryType: 'Transaction',\n        domain: {\n            name: 'OTP EVM claim',\n            version: '1',\n            chainId: '2160',\n            salt: '0x0542e99b538e30d713d3e020f18fa6717eb2c5452bd358e0dd791628260a36f0',\n        },\n        message: {\n            substrateAddress: `${HEX_SUBSTRATE_ADDRESS}`,\n        },\n    };\n\n    const wallet = new Wallet(`${PRIVATE_ETH_KEY}`);\n\n    const digest = _TypedDataEncoder.hash(\n        payload.domain,\n        {\n            Transaction: payload.types.Transaction,\n        },\n        payload.message,\n    );\n\n    const signature = joinSignature(wallet._signingKey().signDigest(digest));\n    console.log('Paste the signature to polkadot.js api evmAddress claimAccount interface:');\n    console.log('==== Signature ====');\n    console.log(signature);\n    console.log('===================');\n}\n\nsign();\n"
  },
  {
    "path": "tools/substrate-accounts-mapping/README.md",
    "content": "# Substrate accounts mapping tool\n\nThis tool:\n- generates one shared management wallet (pair of substrate and eth addresses)\n- generates NUMBER_OF_ACCOUNTS (default 32) operational wallets\n- sends OTP to all substrate accounts\n- performs mapping between substrate and eth pairs\n- sends TRAC to all eth wallets\n- confirms that TRAC is received\n\n## How to run\n\nInside the .env file is stored substrate and eth pair for distribution account that will send OTP and TRAC to newly generated wallets.\nExample of env:\n```\nSUBSTRATE_ACCOUNT_PUBLIC_KEY=\"gJn...\"\nSUBSTRATE_ACCOUNT_PRIVATE_KEY=\"URI FORMAT OF KEY\"\nEVM_ACCOUNT_PUBLIC_KEY=\"0xPublicKey\"\nEVM_ACCOUNT_PRIVATE_KEY=\"0xPrivateKey\"\n```\n\nRun the script:\n```bash\nnode accounts-mapping.js\n```\n\nResult will be stored in `wallets.json` in this format:\n```json\n[\n    {\n        \"evmOperationalWalletPublicKey\": \"\",\n        \"evmOperationalWalletPrivateKey\": \"\",\n        \"substrateOperationalWalletPublicKey\": \"\",\n        \"substrateOperationalWalletPrivateKey\": \"\",\n        \"evmManagementWalletPublicKey\": \"\",\n        \"evmManagementWalletPrivateKey\": \"\",\n        \"substrateManagementWalletPublicKey\": \"\",\n        \"substrateManagementWalletPrivateKey\": \"\"\n    }\n]\n```\n\n## How to modify\n\nTo change number of generated accounts, amount of OTP or TRAC to be sent, or other parameters, following variables should be modified:\n```js\nconst NUMBER_OF_ACCOUNTS = 32;\nconst OTP_AMOUNT = 50 * 1e12; // 50 OTP \nconst TRACE_AMOUNT = '0.000000001';\n\nconst GAS_PRICE = 20;\nconst GAS_LIMIT = 60000; // Estimation is 45260\n```\n\nScript by default script is created to be used for OriginTrail Parachain Mainnet, by modification of following variables it can be used for other parachains:\n```js\nconst TOKEN_ADDRESS = '0xffffffff00000000000000000000000000000001';\nconst HTTPS_ENDPOINT = 'https://astrosat-parachain-rpc.origin-trail.network';\nconst OTP_CHAIN_ID = '2043';\nconst OTP_GENESIS_HASH = '0xe7e0962324a3b86c83404dbea483f25fb5dab4c224791c81b756cfc948006174';\n```\n\n\n"
  },
  {
    "path": "tools/substrate-accounts-mapping/accounts-mapping.js",
    "content": "/* eslint-disable no-await-in-loop */\n/* eslint-disable no-console */\n/* eslint-disable object-shorthand */\n/* eslint-disable lines-between-class-members */\nrequire('dotenv').config({ path: `${__dirname}/../../.env` });\nconst { setTimeout } = require('timers/promises');\nconst appRootPath = require('app-root-path');\nconst { ethers } = require('ethers');\nconst path = require('path');\nconst fs = require('fs');\nconst { ApiPromise, HttpProvider } = require('@polkadot/api');\nconst { Keyring } = require('@polkadot/keyring');\nconst { mnemonicGenerate, mnemonicToMiniSecret, decodeAddress } = require('@polkadot/util-crypto');\nconst { u8aToHex } = require('@polkadot/util');\nconst { Wallet } = require('@ethersproject/wallet');\nconst { joinSignature } = require('@ethersproject/bytes');\nconst { _TypedDataEncoder } = require('@ethersproject/hash');\nconst ERC20Token = require('dkg-evm-module/abi/Token.json');\n\nconst WALLETS_PATH = path.join(appRootPath.path, 'tools/substrate-accounts-mapping/wallets.json');\n\nconst otpAccountWithTokens = {\n    accountPublicKey: process.env.SUBSTRATE_ACCOUNT_PUBLIC_KEY,\n    accountPrivateKey: process.env.SUBSTRATE_ACCOUNT_PRIVATE_KEY,\n};\nconst evmAccountWithTokens = {\n    publicKey: process.env.EVM_ACCOUNT_PUBLIC_KEY,\n    privateKey: process.env.EVM_ACCOUNT_PRIVATE_KEY,\n};\n\nconst TOKEN_ADDRESS = '0xffffffff00000000000000000000000000000001';\nconst HTTPS_ENDPOINT = 'https://astrosat-parachain-rpc.origin-trail.network';\n\nconst NUMBER_OF_ACCOUNTS = 32;\n\nconst OTP_AMOUNT = 50 * 1e12; // 50 OTP <--- Check this!\nconst OTP_CHAIN_ID = '2043';\nconst OTP_GENESIS_HASH = '0xe7e0962324a3b86c83404dbea483f25fb5dab4c224791c81b756cfc948006174';\n\nconst GAS_PRICE = 20;\nconst GAS_LIMIT = 60000; // Estimation is 45260\nconst TRACE_AMOUNT = '0.000000001'; // <--- Check this!\n\nclass AccountsMapping {\n    async initialize() {\n        // Initialise the provider to connect to the local node\n        const provider = new HttpProvider(HTTPS_ENDPOINT);\n\n        // eslint-disable-next-line no-await-in-loop\n        this.parachainProvider = await new ApiPromise({ provider }).isReady;\n        this.ethersProvider = new ethers.providers.JsonRpcProvider(HTTPS_ENDPOINT);\n        this.evmWallet = new ethers.Wallet(evmAccountWithTokens.privateKey, this.ethersProvider);\n        this.initialized = true;\n        this.tokenContract = new ethers.Contract(TOKEN_ADDRESS, ERC20Token.abi, this.evmWallet);\n    }\n\n    async mapAccounts() {\n        if (!this.initialized) {\n            await this.initialize();\n        }\n\n        const currentWallets = [];\n\n        console.log(`Generating, mapping and funding management wallet`);\n        const {\n            evmPublicKey: evmManagementWalletPublicKey,\n            evmPrivateKey: evmManagementWalletPrivateKey,\n            substratePublicKey: substrateManagementWalletPublicKey,\n            substratePrivateKey: substrateManagementWalletPrivateKey,\n        } = await this.generateWallets();\n\n        // Fund management wallet\n        await this.fundAccountsWithOtp(substrateManagementWalletPublicKey);\n\n        // Generate and fund all other wallets\n        for (let i = 0; i < NUMBER_OF_ACCOUNTS; i += 1) {\n            console.log(`Generating and funding with OTP wallet #${i + 1}`);\n\n            const {\n                evmPublicKey: evmOperationalWalletPublicKey,\n                evmPrivateKey: evmOperationalWalletPrivateKey,\n                substratePublicKey: substrateOperationalWalletPublicKey,\n                substratePrivateKey: substrateOperationalWalletPrivateKey,\n            } = await this.generateWallets();\n\n            await this.fundAccountsWithOtp(substrateOperationalWalletPublicKey);\n\n            // Store new wallets\n            currentWallets.push({\n                evmOperationalWalletPublicKey,\n                evmOperationalWalletPrivateKey,\n                substrateOperationalWalletPublicKey,\n                substrateOperationalWalletPrivateKey,\n                evmManagementWalletPublicKey,\n                evmManagementWalletPrivateKey,\n                substrateManagementWalletPublicKey,\n                substrateManagementWalletPrivateKey,\n            });\n            await fs.promises.writeFile(WALLETS_PATH, JSON.stringify(currentWallets, null, 4));\n        }\n        console.log('Waiting 35s for funding TXs to get into block!');\n        await this.sleepForMilliseconds(35 * 1000);\n        console.log(`${NUMBER_OF_ACCOUNTS} wallets are generated and funded with OTP!`);\n\n        console.log(`Executing mapping!`);\n        // Map the management wallet\n        await this.mapWallet(\n            evmManagementWalletPublicKey,\n            evmManagementWalletPrivateKey,\n            substrateManagementWalletPublicKey,\n            substrateManagementWalletPrivateKey,\n        );\n        // Map all operational wallets\n        for (const wallet of currentWallets) {\n            await this.mapWallet(\n                wallet.evmOperationalWalletPublicKey,\n                wallet.evmOperationalWalletPrivateKey,\n                wallet.substrateOperationalWalletPublicKey,\n                wallet.substrateOperationalWalletPrivateKey,\n            );\n        }\n        console.log('Waiting 35s for mapping TXs to get into block!');\n        await this.sleepForMilliseconds(35 * 1000);\n        console.log(`${NUMBER_OF_ACCOUNTS} wallets mapped!`);\n\n        console.log(`Funding wallets with TRAC!`);\n        let nonce = await this.evmWallet.getTransactionCount();\n        // Fund management wallet with TRACE\n        this.fundAccountsWithTrac(evmManagementWalletPublicKey, nonce);\n        // Fund rest of wallets\n        for (const wallet of currentWallets) {\n            if (await this.accountMapped(wallet.evmOperationalWalletPublicKey)) {\n                nonce += 1;\n                this.fundAccountsWithTrac(wallet.evmOperationalWalletPublicKey, nonce);\n            } else {\n                console.log(`Mapping failed or not finished for account: ${wallet}`);\n            }\n        }\n        console.log('Waiting for Trac TXs to get into block!');\n        await this.sleepForMilliseconds(35 * 1000);\n        console.log(`${NUMBER_OF_ACCOUNTS} wallets funded with TRAC!`);\n\n        // Check the balance of new accounts\n        for (const wallet of currentWallets) {\n            const tokenBalance = await this.tokenContract.methods\n                .balanceOf(wallet.evmOperationalWalletPublicKey)\n                .call();\n            console.log(\n                `New balance of ${wallet.evmOperationalWalletPublicKey} is ${tokenBalance} TRAC`,\n            );\n        }\n    }\n\n    async generateWallets() {\n        const { evmPublicKey, evmPrivateKey } = await this.generateEVMAccount();\n        const { substratePublicKey, substratePrivateKey } = await this.generateSubstrateAccount();\n        return {\n            evmPublicKey,\n            evmPrivateKey,\n            substratePublicKey,\n            substratePrivateKey,\n        };\n    }\n    async generateSubstrateAccount() {\n        const keyring = new Keyring({ type: 'sr25519' });\n        keyring.setSS58Format(101);\n\n        const mnemonic = mnemonicGenerate();\n        const mnemonicMini = mnemonicToMiniSecret(mnemonic);\n        const substratePrivateKey = u8aToHex(mnemonicMini);\n        const substratePublicKey = keyring.createFromUri(substratePrivateKey).address;\n        return {\n            substratePublicKey,\n            substratePrivateKey,\n        };\n    }\n\n    async generateEVMAccount() {\n        const { address, privateKey } = await ethers.Wallet.createRandom();\n        return { evmPublicKey: address, evmPrivateKey: privateKey };\n    }\n\n    async mapWallet(evmPublicKey, evmPrivateKey, substratePublicKey, substratePrivateKey) {\n        const signature = await this.sign(substratePublicKey, evmPrivateKey);\n        const keyring = new Keyring({ type: 'sr25519' });\n        keyring.setSS58Format(101);\n        const result = await this.callParachainExtrinsic(\n            keyring.addFromSeed(substratePrivateKey),\n            'evmAccounts',\n            'claimAccount',\n            [evmPublicKey, signature],\n        );\n        if (result.toHex() === '0x') throw Error('Unable to create account mapping for otp');\n        console.log(result.toString());\n        console.log(`Account mapped for evm public key: ${evmPublicKey}`);\n    }\n\n    async fundAccountsWithOtp(substratePublicKey) {\n        const keyring = new Keyring({ type: 'sr25519' });\n        keyring.setSS58Format(101);\n        const uriKeyring = keyring.addFromSeed(otpAccountWithTokens.accountPrivateKey);\n        return this.callParachainExtrinsic(uriKeyring, 'balances', 'transfer', [\n            substratePublicKey,\n            OTP_AMOUNT,\n        ]);\n    }\n\n    async fundAccountsWithTrac(evmWallet, nonce) {\n        this.tokenContract.transfer(evmWallet, ethers.utils.parseEther(TRACE_AMOUNT), {\n            gasPrice: GAS_PRICE,\n            gasLimit: GAS_LIMIT,\n            nonce: nonce,\n        });\n    }\n\n    async accountMapped(wallet) {\n        const result = await this.queryParachainState('evmAccounts', 'accounts', [wallet]);\n        return result && result.toHex() !== '0x';\n    }\n\n    async callParachainExtrinsic(keyring, extrinsic, method, args) {\n        // console.log(`Calling parachain extrinsic : ${extrinsic}, method: ${method}`);\n        return this.parachainProvider.tx[extrinsic][method](...args).signAndSend(keyring, {\n            nonce: -1,\n        });\n    }\n\n    async queryParachainState(state, method, args) {\n        return this.parachainProvider.query[state][method](...args);\n    }\n\n    async sleepForMilliseconds(milliseconds) {\n        await setTimeout(milliseconds);\n    }\n\n    async sign(publicAccountKey, privateEthKey) {\n        const hexPubKey = u8aToHex(decodeAddress(publicAccountKey));\n        console.log(`Hex account pub: ${hexPubKey}`);\n        const payload = {\n            types: {\n                EIP712Domain: [\n                    {\n                        name: 'name',\n                        type: 'string',\n                    },\n                    {\n                        name: 'version',\n                        type: 'string',\n                    },\n                    {\n                        name: 'chainId',\n                        type: 'uint256',\n                    },\n                    {\n                        name: 'salt',\n                        type: 'bytes32',\n                    },\n                ],\n                Transaction: [\n                    {\n                        name: 'substrateAddress',\n                        type: 'bytes',\n                    },\n                ],\n            },\n            primaryType: 'Transaction',\n            domain: {\n                name: 'OTP EVM claim',\n                version: '1',\n                chainId: OTP_CHAIN_ID,\n                salt: OTP_GENESIS_HASH,\n            },\n            message: {\n                substrateAddress: hexPubKey,\n            },\n        };\n\n        const wallet = new Wallet(privateEthKey);\n\n        const digest = _TypedDataEncoder.hash(\n            payload.domain,\n            {\n                Transaction: payload.types.Transaction,\n            },\n            payload.message,\n        );\n\n        const signature = joinSignature(wallet._signingKey().signDigest(digest));\n        return signature;\n    }\n}\nconst am = new AccountsMapping();\nam.mapAccounts();\n"
  },
  {
    "path": "tools/token-generation.js",
    "content": "/* eslint no-console: 0 */\nimport 'dotenv/config';\nimport ms from 'ms';\nimport DeepExtend from 'deep-extend';\nimport rc from 'rc';\nimport fs from 'fs-extra';\nimport { v4 as uuid } from 'uuid';\nimport { createRequire } from 'module';\nimport Logger from '../src/logger/logger.js';\nimport RepositoryModuleManager from '../src/modules/repository/repository-module-manager.js';\nimport jwtUtil from '../src/service/util/jwt-util.js';\n\nconst require = createRequire(import.meta.url);\nconst configjson = require('../config/config.json');\nconst pjson = require('../package.json');\n\nconst getLogger = () => new Logger('silent', false);\nlet repository;\n\nconst getConfig = () => {\n    let config;\n    let userConfig;\n\n    if (process.env.USER_CONFIG_PATH) {\n        const configurationFilename = process.env.USER_CONFIG_PATH;\n        const pathSplit = configurationFilename.split('/');\n        userConfig = JSON.parse(fs.readFileSync(configurationFilename));\n        userConfig.configFilename = pathSplit[pathSplit.length - 1];\n    }\n\n    const defaultConfig = JSON.parse(JSON.stringify(configjson[process.env.NODE_ENV]));\n\n    if (userConfig) {\n        config = DeepExtend(defaultConfig, userConfig);\n    } else {\n        config = rc(pjson.name, defaultConfig);\n    }\n\n    if (!config.configFilename) {\n        // set default user configuration filename\n        config.configFilename = '.origintrail_noderc';\n    }\n    return config;\n};\n\nconst loadRepository = async () => {\n    repository = new RepositoryModuleManager({ logger: getLogger(), config: getConfig() });\n    await repository.initialize();\n};\n\n/**\n * Returns argument from argv\n * @param argName\n * @returns {string|null}\n */\nconst getArg = (argName) => {\n    const args = process.argv;\n    const arg = args.find((a) => a.startsWith(argName));\n\n    if (!arg) {\n        return null;\n    }\n\n    const argSplit = arg.split('=');\n\n    if (!arg || argSplit.length < 2 || !argSplit[1]) {\n        return null;\n    }\n\n    return argSplit[1];\n};\n/**\n * Returns user's name from arguments\n * @returns {string}\n */\nconst getUserFromArgs = () => {\n    const arg = getArg('--user');\n\n    if (!arg) {\n        return 'node-runner';\n    }\n\n    return arg;\n};\n\n/**\n * Returns expiresAt from arguments\n * If no expiresAt is provided, null is returned\n * Expressed in seconds or a string describing a time span zeit/ms\n * @returns {string|null}\n */\nconst getExpiresInArg = () => {\n    const arg = getArg('--expiresIn');\n\n    if (!arg) {\n        return null;\n    }\n\n    if (!ms(arg)) {\n        console.log('\\x1b[31m[ERROR]\\x1b[0m Invalid value for expiresIn argument');\n        process.exit(1);\n    }\n\n    return arg;\n};\n\n/**\n * Returns expiresAt from arguments\n * If no expiresAt is provided, null is returned\n * Expressed in seconds or a string describing a time span zeit/ms\n * @returns {string|null}\n */\nconst getTokenName = () => {\n    const arg = getArg('--tokenName');\n\n    if (!arg) {\n        console.log('\\x1b[31m[ERROR]\\x1b[0m Missing mandatory tokenName argument.');\n        process.exit(1);\n    }\n\n    return arg;\n};\n\nconst saveTokenData = async (tokenId, userId, tokenName, expiresIn) => {\n    let expiresAt = null;\n\n    if (expiresIn) {\n        const time = new Date().getTime() + ms(expiresIn);\n        expiresAt = new Date(time);\n    }\n\n    await repository.saveToken(tokenId, userId, tokenName, expiresAt);\n};\n\nconst printMessage = (token, hasExpiryDate) => {\n    console.log('\\x1b[32mAccess token successfully created.\\x1b[0m ');\n\n    if (!hasExpiryDate) {\n        console.log('\\x1b[33m[WARNING] Created token has no expiry date. \\x1b[0m ');\n    }\n\n    console.log(token);\n    console.log(\n        '\\x1b[32mMake sure to copy your personal access token now. You won’t be able to see it again!\\x1b[0m ',\n    );\n};\n\nconst getUserId = async (username) => {\n    const user = await repository.getUser(username);\n\n    if (!user) {\n        console.log(`\\x1b[31m[ERROR]\\x1b[0m User ${username} doesn't exist.`);\n        process.exit(1);\n    }\n\n    return user.id;\n};\n\nconst generateToken = async () => {\n    const username = getUserFromArgs();\n    const expiresIn = getExpiresInArg();\n    const tokenName = getTokenName();\n\n    await loadRepository();\n\n    const userId = await getUserId(username);\n    const tokenId = uuid();\n\n    await saveTokenData(tokenId, userId, tokenName, expiresIn);\n\n    const token = jwtUtil.generateJWT(tokenId, expiresIn);\n\n    printMessage(token, expiresIn);\n    process.exit(0);\n};\n\ngenerateToken();\n"
  },
  {
    "path": "v8-data-migration/abi/ContentAssetStorage.json",
    "content": "[\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"hubAddress\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"constructor\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"approved\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Approval\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"bool\",\n        \"name\": \"approved\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"ApprovalForAll\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Transfer\",\n    \"type\": \"event\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"approve\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assetAssertionId\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"name\": \"assertionExists\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"balanceOf\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"burn\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"index\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"deleteAssertionIssuer\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"deleteAsset\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"generateTokenId\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getApproved\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"index\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIdByIndex\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIds\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bytes32[]\",\n        \"name\": \"\",\n        \"type\": \"bytes32[]\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIdsLength\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"assertionIndex\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIssuer\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAsset\",\n    \"outputs\": [\n      {\n        \"components\": [\n          {\n            \"internalType\": \"bool\",\n            \"name\": \"immutable_\",\n            \"type\": \"bool\"\n          },\n          {\n            \"internalType\": \"bytes32[]\",\n            \"name\": \"assertionIds\",\n            \"type\": \"bytes32[]\"\n          }\n        ],\n        \"internalType\": \"struct ContentAssetStructs.Asset\",\n        \"name\": \"\",\n        \"type\": \"tuple\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getLatestAssertionId\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"hub\",\n    \"outputs\": [\n      {\n        \"internalType\": \"contract Hub\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"isApprovedForAll\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"isMutable\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"name\": \"issuers\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"mint\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"name\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"ownerOf\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"name\": \"pushAssertionId\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"safeTransferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes\",\n        \"name\": \"data\",\n        \"type\": \"bytes\"\n      }\n    ],\n    \"name\": \"safeTransferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"approved\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"setApprovalForAll\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"issuer\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"setAssertionIssuer\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"immutable_\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"setMutability\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"bytes4\",\n        \"name\": \"interfaceId\",\n        \"type\": \"bytes4\"\n      }\n    ],\n    \"name\": \"supportsInterface\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"symbol\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"tokenURI\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"transferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"version\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"pure\",\n    \"type\": \"function\"\n  }\n]\n"
  },
  {
    "path": "v8-data-migration/abi/ContentAssetStorageV2.json",
    "content": "[\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"hubAddress\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"constructor\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"NoMintedAssets\",\n    \"type\": \"error\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"approved\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Approval\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"bool\",\n        \"name\": \"approved\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"ApprovalForAll\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"_fromTokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"_toTokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"BatchMetadataUpdate\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": false,\n        \"internalType\": \"uint256\",\n        \"name\": \"_tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"MetadataUpdate\",\n    \"type\": \"event\"\n  },\n  {\n    \"anonymous\": false,\n    \"inputs\": [\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"indexed\": true,\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"Transfer\",\n    \"type\": \"event\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"approve\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assetAssertionId\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"name\": \"assertionExists\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"balanceOf\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"burn\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"index\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"deleteAssertionIssuer\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"deleteAsset\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"generateTokenId\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getApproved\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"index\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIdByIndex\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIds\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bytes32[]\",\n        \"name\": \"\",\n        \"type\": \"bytes32[]\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIdsLength\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"assertionIndex\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAssertionIssuer\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getAsset\",\n    \"outputs\": [\n      {\n        \"components\": [\n          {\n            \"internalType\": \"bool\",\n            \"name\": \"immutable_\",\n            \"type\": \"bool\"\n          },\n          {\n            \"internalType\": \"bytes32[]\",\n            \"name\": \"assertionIds\",\n            \"type\": \"bytes32[]\"\n          }\n        ],\n        \"internalType\": \"struct ContentAssetStructs.Asset\",\n        \"name\": \"\",\n        \"type\": \"tuple\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"getLatestAssertionId\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"hub\",\n    \"outputs\": [\n      {\n        \"internalType\": \"contract Hub\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"owner\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"isApprovedForAll\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"isMutable\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"name\": \"issuers\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"lastTokenId\",\n    \"outputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"mint\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"name\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"ownerOf\",\n    \"outputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"\",\n        \"type\": \"address\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      }\n    ],\n    \"name\": \"pushAssertionId\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"safeTransferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes\",\n        \"name\": \"data\",\n        \"type\": \"bytes\"\n      }\n    ],\n    \"name\": \"safeTransferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"operator\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"approved\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"setApprovalForAll\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bytes32\",\n        \"name\": \"assertionId\",\n        \"type\": \"bytes32\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"issuer\",\n        \"type\": \"address\"\n      }\n    ],\n    \"name\": \"setAssertionIssuer\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"baseURI\",\n        \"type\": \"string\"\n      }\n    ],\n    \"name\": \"setBaseURI\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      },\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"immutable_\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"name\": \"setMutability\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"bytes4\",\n        \"name\": \"interfaceId\",\n        \"type\": \"bytes4\"\n      }\n    ],\n    \"name\": \"supportsInterface\",\n    \"outputs\": [\n      {\n        \"internalType\": \"bool\",\n        \"name\": \"\",\n        \"type\": \"bool\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"symbol\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"tokenBaseURI\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"tokenURI\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"view\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [\n      {\n        \"internalType\": \"address\",\n        \"name\": \"from\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"address\",\n        \"name\": \"to\",\n        \"type\": \"address\"\n      },\n      {\n        \"internalType\": \"uint256\",\n        \"name\": \"tokenId\",\n        \"type\": \"uint256\"\n      }\n    ],\n    \"name\": \"transferFrom\",\n    \"outputs\": [],\n    \"stateMutability\": \"nonpayable\",\n    \"type\": \"function\"\n  },\n  {\n    \"inputs\": [],\n    \"name\": \"version\",\n    \"outputs\": [\n      {\n        \"internalType\": \"string\",\n        \"name\": \"\",\n        \"type\": \"string\"\n      }\n    ],\n    \"stateMutability\": \"pure\",\n    \"type\": \"function\"\n  }\n]\n"
  },
  {
    "path": "v8-data-migration/blockchain-utils.js",
    "content": "import { ethers } from 'ethers';\nimport { BLOCKCHAINS, ABIs, CONTENT_ASSET_STORAGE_CONTRACT } from './constants.js';\nimport {\n    validateProvider,\n    validateStorageContractAddress,\n    validateStorageContractName,\n    validateStorageContractAbi,\n    validateBlockchainDetails,\n} from './validation.js';\nimport logger from './logger.js';\n\nfunction maskRpcUrl(url) {\n    // Validation\n    if (!url || typeof url !== 'string') {\n        throw new Error(`URL is not defined or it is not a string. URL: ${url}`);\n    }\n\n    if (url.includes('apiKey')) {\n        return url.split('apiKey')[0];\n    }\n    return url;\n}\n\n// Initialize rpc\nexport async function initializeRpc(rpcEndpoint) {\n    // Validation\n    if (!rpcEndpoint || typeof rpcEndpoint !== 'string') {\n        logger.error(\n            `RPC endpoint is not defined or it is not a string. RPC endpoint: ${rpcEndpoint}`,\n        );\n        process.exit(1);\n    }\n    // initialize all possible providers\n    const Provider = ethers.providers.JsonRpcProvider;\n\n    try {\n        const provider = new Provider(rpcEndpoint);\n        // eslint-disable-next-line no-await-in-loop\n        await provider.getNetwork();\n        logger.info(`Connected to the blockchain RPC: ${maskRpcUrl(rpcEndpoint)}.`);\n        return provider;\n    } catch (e) {\n        logger.error(`Unable to connect to the blockchain RPC: ${maskRpcUrl(rpcEndpoint)}.`);\n        process.exit(1);\n    }\n}\n\nexport async function getStorageContractAndAddress(\n    provider,\n    storageContractAddress,\n    storageContractName,\n    storageContractAbi,\n) {\n    // Validation\n    validateProvider(provider);\n    validateStorageContractAddress(storageContractAddress);\n    validateStorageContractName(storageContractName);\n    validateStorageContractAbi(storageContractAbi);\n\n    logger.info(\n        `Initializing asset contract: ${storageContractName} with address: ${storageContractAddress}`,\n    );\n    // initialize asset contract\n    const storageContract = new ethers.Contract(\n        storageContractAddress,\n        storageContractAbi,\n        provider,\n    );\n\n    logger.info(\n        `Contract ${storageContractName} initialized with address: ${storageContractAddress}`,\n    );\n    return storageContract;\n}\n\nexport async function getContentAssetStorageContract(provider, blockchainDetails) {\n    // Validation\n    validateProvider(provider);\n    validateBlockchainDetails(blockchainDetails);\n\n    const contentAssetStorageContarct =\n        blockchainDetails.NAME === BLOCKCHAINS.NEUROWEB_TESTNET.NAME ||\n        blockchainDetails.NAME === BLOCKCHAINS.NEUROWEB_MAINNET.NAME\n            ? ABIs.ContentAssetStorage\n            : ABIs.ContentAssetStorageV2;\n    const storageContract = await getStorageContractAndAddress(\n        provider,\n        blockchainDetails.CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS,\n        CONTENT_ASSET_STORAGE_CONTRACT,\n        contentAssetStorageContarct,\n    );\n\n    return storageContract;\n}\n"
  },
  {
    "path": "v8-data-migration/constants.js",
    "content": "import { createRequire } from 'module';\n\n// Triple store constants\nexport const SCHEMA_CONTEXT = 'http://schema.org/';\nexport const METADATA_NAMED_GRAPH = 'metadata:graph';\nexport const PRIVATE_ASSERTION_ONTOLOGY =\n    '<https://ontology.origintrail.io/dkg/1.0#privateAssertionID>';\nexport const TRIPLE_STORE_CONNECT_MAX_RETRIES = 10;\nexport const TRIPLE_STORE_CONNECT_RETRY_FREQUENCY = 10;\nexport const N_QUADS = 'application/n-quads';\nexport const OT_BLAZEGRAPH = 'ot-blazegraph';\nexport const OT_FUSEKI = 'ot-fuseki';\nexport const OT_GRAPHDB = 'ot-graphdb';\nexport const PRIVATE_CURRENT = 'privateCurrent';\nexport const PUBLIC_CURRENT = 'publicCurrent';\nexport const DKG_REPOSITORY = 'dkg';\nexport const VISIBILITY = {\n    PUBLIC: 'public',\n    PRIVATE: 'private',\n};\nexport const BATCH_SIZE = 50;\n\nexport const MAIN_DIR = '/root';\nexport const DEFAULT_CONFIG_PATH = `${MAIN_DIR}/ot-node/current/config/config.json`;\nexport const NODERC_CONFIG_PATH = `${MAIN_DIR}/ot-node/.origintrail_noderc`;\nexport const DATA_MIGRATION_DIR = `${MAIN_DIR}/ot-node/data/data-migration`;\nexport const LOG_DIR = `${MAIN_DIR}/ot-node/data/data-migration/logs`;\nexport const ENV_PATH = `${MAIN_DIR}/ot-node/current/.env`;\nexport const MIGRATION_DIR = `${MAIN_DIR}/ot-node/data/migrations/`;\nexport const MIGRATION_PROGRESS_FILE = 'v8DataMigration';\n\nexport const DB_URLS = {\n    testnet: 'https://hosting.origin-trail.network/csv/testnet.db',\n    mainnet: 'https://hosting.origin-trail.network/csv/mainnet.db',\n};\n\nconst require = createRequire(import.meta.url);\n\nexport const ABIs = {\n    ContentAssetStorageV2: require('./abi/ContentAssetStorageV2.json'),\n    ContentAssetStorage: require('./abi/ContentAssetStorage.json'),\n};\nexport const BLOCKCHAINS = {\n    BASE_DEVNET: {\n        ID: 'base:84532',\n        ENV: 'devnet',\n        NAME: 'base_devnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0xbe08a25dcf2b68af88501611e5456571f50327b4',\n    },\n    BASE_TESTNET: {\n        ID: 'base:84532',\n        ENV: 'testnet',\n        NAME: 'base_testnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0x9e3071dc0730cb6dd0ce42969396d716ea33e7e1',\n    },\n    BASE_MAINNET: {\n        ID: 'base:8453',\n        ENV: 'mainnet',\n        NAME: 'base_mainnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0x3bdfa81079b2ba53a25a6641608e5e1e6c464597',\n    },\n    GNOSIS_DEVNET: {\n        ID: 'gnosis:10200',\n        ENV: 'devnet',\n        NAME: 'gnosis_devnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0x3db64dd0ac054610d1e2af9cca0fbcb1a7f4c2d8',\n    },\n    GNOSIS_TESTNET: {\n        ID: 'gnosis:10200',\n        ENV: 'testnet',\n        NAME: 'gnosis_testnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0xea3423e02c8d231532dab1bce5d034f3737b3638',\n    },\n    GNOSIS_MAINNET: {\n        ID: 'gnosis:100',\n        ENV: 'mainnet',\n        NAME: 'gnosis_mainnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0xf81a8c0008de2dcdb73366cf78f2b178616d11dd',\n    },\n    NEUROWEB_TESTNET: {\n        ID: 'otp:20430',\n        ENV: 'testnet',\n        NAME: 'neuroweb_testnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0x1a061136ed9f5ed69395f18961a0a535ef4b3e5f',\n    },\n    NEUROWEB_MAINNET: {\n        ID: 'otp:2043',\n        ENV: 'mainnet',\n        NAME: 'neuroweb_mainnet',\n        CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS: '0x5cac41237127f94c2d21dae0b14bfefa99880630',\n    },\n};\n\nexport const CONTENT_ASSET_STORAGE_CONTRACT = 'ContentAssetStorage';\n"
  },
  {
    "path": "v8-data-migration/logger.js",
    "content": "import pino from 'pino';\nimport fs from 'fs';\nimport { LOG_DIR } from './constants.js';\n\n// Ensure logs directory exists\nif (!fs.existsSync(LOG_DIR)) {\n    fs.mkdirSync(LOG_DIR, { recursive: true });\n\n    if (!fs.existsSync(LOG_DIR)) {\n        throw new Error(\n            `Something went wrong. Directory: ${LOG_DIR} does not exist after creation.`,\n        );\n    }\n}\n\nconst timers = new Map();\n\nconst baseLogger = pino({\n    transport: {\n        targets: [\n            {\n                target: 'pino-pretty',\n                level: 'info',\n                options: {\n                    colorize: true,\n                    translateTime: 'yyyy-mm-dd HH:MM:ss',\n                },\n            },\n            {\n                target: 'pino-pretty',\n                level: 'info',\n                options: {\n                    destination: `${LOG_DIR}/migration.log`,\n                    colorize: false,\n                    translateTime: 'yyyy-mm-dd HH:MM:ss',\n                },\n            },\n        ],\n    },\n});\n\n// Create enhanced logger with proper method binding\nconst logger = {\n    // Bind all methods from the base logger\n    info: baseLogger.info.bind(baseLogger),\n    error: baseLogger.error.bind(baseLogger),\n    warn: baseLogger.warn.bind(baseLogger),\n    debug: baseLogger.debug.bind(baseLogger),\n\n    // Add our custom timing methods\n    time(label) {\n        timers.set(label, performance.now());\n    },\n    timeEnd(label) {\n        const start = timers.get(label);\n        if (!start) {\n            this.warn(`Timer '${label}' does not exist`);\n            return;\n        }\n        const duration = (performance.now() - start).toFixed(3);\n        timers.delete(label);\n        this.info(`${label}: ${duration}ms`);\n        return duration;\n    },\n};\n\nexport default logger;\n"
  },
  {
    "path": "v8-data-migration/run-data-migration.sh",
    "content": "MAIN_DIR=/root\n\ncd $MAIN_DIR/ot-node/current/v8-data-migration/ &&\nnpm rebuild sqlite3 &&\nnohup node v8-data-migration.js > $MAIN_DIR/ot-node/data/nohup.out 2>&1 &"
  },
  {
    "path": "v8-data-migration/sqlite-utils.js",
    "content": "import sqlite3 from 'sqlite3';\nimport { open } from 'sqlite';\nimport path from 'path';\nimport { DATA_MIGRATION_DIR } from './constants.js';\nimport logger from './logger.js';\n\nexport class SqliteDatabase {\n    constructor() {\n        this.db = null;\n    }\n\n    async initialize() {\n        if (this.db) {\n            return;\n        }\n\n        const dbPath = path.join(DATA_MIGRATION_DIR, `${process.env.NODE_ENV}.db`);\n\n        this.db = await open({\n            filename: dbPath,\n            driver: sqlite3.Database,\n        });\n\n        if (!this.db) {\n            logger.error('Failed to initialize SQLite database');\n            process.exit(1);\n        }\n    }\n\n    async checkIntegrity() {\n        this._validateConnection();\n\n        try {\n            const result = await this.db.get('PRAGMA integrity_check;');\n            if (result.integrity_check === 'ok') {\n                logger.info('Database integrity check passed.');\n                return true;\n            }\n            logger.error('Database integrity check failed:', result.integrity_check);\n            return false;\n        } catch (error) {\n            logger.error('Error during integrity check:', error.message);\n            return false;\n        }\n    }\n\n    async getTableExists(blockchainName) {\n        this._validateConnection();\n        this._validateBlockchainName(blockchainName);\n\n        const tableExists = await this.db.get(\n            `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,\n            [blockchainName],\n        );\n\n        return tableExists;\n    }\n\n    async getBatchOfUnprocessedTokenIds(blockchainName, batchSize) {\n        this._validateConnection();\n        this._validateBlockchainName(blockchainName);\n\n        const rows = await this.db.all(\n            `\n            SELECT token_id, ual, assertion_id\n            FROM ${blockchainName}\n            WHERE processed = 0\n            LIMIT ?\n        `,\n            batchSize,\n        );\n\n        const batchData = {};\n        rows.forEach((row) => {\n            batchData[row.token_id] = {\n                ual: row.ual,\n                assertionId: row.assertion_id,\n                processed: 'false',\n            };\n        });\n\n        return batchData;\n    }\n\n    async markRowsAsProcessed(blockchainName, tokenIds) {\n        this._validateConnection();\n        this._validateBlockchainName(blockchainName);\n\n        const placeholders = tokenIds.map(() => '?').join(',');\n        await this.db.run(\n            `\n            UPDATE ${blockchainName}\n            SET processed = 1 \n            WHERE token_id IN (${placeholders})\n        `,\n            tokenIds,\n        );\n    }\n\n    async getHighestTokenId(blockchainName) {\n        this._validateConnection();\n        this._validateBlockchainName(blockchainName);\n\n        const result = await this.db.get(`SELECT MAX(token_id) as max_id FROM ${blockchainName}`);\n        return result.max_id;\n    }\n\n    async getUnprocessedCount(blockchainName) {\n        this._validateConnection();\n        this._validateBlockchainName(blockchainName);\n\n        const result = await this.db.get(`\n            SELECT COUNT(*) as count \n            FROM ${blockchainName} \n            WHERE processed = 0\n        `);\n        return result.count;\n    }\n\n    async insertAssertion(blockchainName, tokenId, ual, assertionId) {\n        this._validateConnection();\n        this._validateBlockchainName(blockchainName);\n\n        try {\n            await this.db.run(\n                `INSERT OR IGNORE INTO ${blockchainName} (token_id, ual, assertion_id, processed) VALUES (?, ?, ?, 1)`,\n                [tokenId, ual, assertionId],\n            );\n            return true;\n        } catch (error) {\n            logger.error(`Error inserting assertion into ${blockchainName} table:`, error.message);\n            return false;\n        }\n    }\n\n    async close() {\n        if (this.db) {\n            await this.db.close();\n            this.db = null;\n        }\n    }\n\n    _validateConnection() {\n        if (!this.db) {\n            logger.error('Database not initialized. Call initialize() first.');\n            process.exit(1);\n        }\n    }\n\n    _validateBlockchainName(blockchainName) {\n        if (!blockchainName) {\n            logger.error('Blockchain name is required');\n            process.exit(1);\n        }\n    }\n}\n\n// Export a single instance\nconst sqliteDb = new SqliteDatabase();\nexport default sqliteDb;\n"
  },
  {
    "path": "v8-data-migration/triple-store-utils.js",
    "content": "import { setTimeout } from 'timers/promises';\nimport axios from 'axios';\nimport {\n    OT_BLAZEGRAPH,\n    OT_FUSEKI,\n    OT_GRAPHDB,\n    N_QUADS,\n    SCHEMA_CONTEXT,\n    PRIVATE_ASSERTION_ONTOLOGY,\n    PUBLIC_CURRENT,\n    PRIVATE_CURRENT,\n    DKG_REPOSITORY,\n    VISIBILITY,\n    METADATA_NAMED_GRAPH,\n    TRIPLE_STORE_CONNECT_MAX_RETRIES,\n    TRIPLE_STORE_CONNECT_RETRY_FREQUENCY,\n} from './constants.js';\nimport {\n    validateTripleStoreConfig,\n    validateTripleStoreRepositories,\n    validateTripleStoreImplementation,\n    validateRepository,\n    validateQuery,\n    validateAssertionId,\n    validateTokenId,\n    validateAssertion,\n    validateUal,\n} from './validation.js';\nimport logger from './logger.js';\n\nexport function getTripleStoreData(tripleStoreConfig) {\n    // Validation\n    validateTripleStoreConfig(tripleStoreConfig);\n\n    let tripleStoreImplementation;\n    const tripleStoreRepositories = {};\n\n    for (const [implementationName, implementationDetails] of Object.entries(\n        tripleStoreConfig.implementation,\n    )) {\n        if (implementationDetails.enabled) {\n            tripleStoreImplementation = implementationName;\n            for (const [repository, repositoryDetails] of Object.entries(\n                implementationDetails.config.repositories,\n            )) {\n                if (\n                    repository === PRIVATE_CURRENT ||\n                    repository === PUBLIC_CURRENT ||\n                    repository === DKG_REPOSITORY\n                ) {\n                    tripleStoreRepositories[repository] = repositoryDetails;\n                }\n            }\n            break;\n        }\n    }\n\n    return { tripleStoreImplementation, tripleStoreRepositories };\n}\n\n// Initialize sparql endpoints\nfunction initializeSparqlEndpoints(tripleStoreRepositories, repository, tripleStoreImplementation) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    const { url, name } = tripleStoreRepositories[repository];\n    const updatedTripleStoreRepositories = tripleStoreRepositories;\n\n    switch (tripleStoreImplementation) {\n        case OT_BLAZEGRAPH:\n            updatedTripleStoreRepositories[\n                repository\n            ].sparqlEndpoint = `${url}/blazegraph/namespace/${name}/sparql`;\n            updatedTripleStoreRepositories[\n                repository\n            ].sparqlEndpointUpdate = `${url}/blazegraph/namespace/${name}/sparql`;\n            break;\n        case OT_FUSEKI:\n            updatedTripleStoreRepositories[repository].sparqlEndpoint = `${url}/${name}/sparql`;\n            updatedTripleStoreRepositories[\n                repository\n            ].sparqlEndpointUpdate = `${url}/${name}/update`;\n            break;\n        case OT_GRAPHDB:\n            updatedTripleStoreRepositories[\n                repository\n            ].sparqlEndpoint = `${url}/repositories/${name}`;\n            updatedTripleStoreRepositories[\n                repository\n            ].sparqlEndpointUpdate = `${url}/repositories/${name}/statements`;\n            break;\n        default:\n            throw new Error('Invalid triple store name in initializeSparqlEndpoints');\n    }\n\n    return updatedTripleStoreRepositories;\n}\n\nexport function initializeRepositories(tripleStoreRepositories, tripleStoreImplementation) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    let updatedTripleStoreRepositories = tripleStoreRepositories;\n    for (const repository in tripleStoreRepositories) {\n        logger.info(`Initializing a triple store repository: ${repository}`);\n        updatedTripleStoreRepositories = initializeSparqlEndpoints(\n            updatedTripleStoreRepositories,\n            repository,\n            tripleStoreImplementation,\n        );\n    }\n\n    return updatedTripleStoreRepositories;\n}\n\nexport function initializeContexts(tripleStoreRepositories) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n\n    const updatedTripleStoreRepositories = tripleStoreRepositories;\n\n    for (const repository in updatedTripleStoreRepositories) {\n        const sources = [\n            {\n                type: 'sparql',\n                value: updatedTripleStoreRepositories[repository].sparqlEndpoint,\n            },\n        ];\n\n        updatedTripleStoreRepositories[repository].updateContext = {\n            sources,\n            destination: {\n                type: 'sparql',\n                value: updatedTripleStoreRepositories[repository].sparqlEndpointUpdate,\n            },\n        };\n        updatedTripleStoreRepositories[repository].queryContext = {\n            sources,\n        };\n    }\n\n    return updatedTripleStoreRepositories;\n}\n\nexport async function healthCheck(tripleStoreRepositories, repository, tripleStoreImplementation) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    switch (tripleStoreImplementation) {\n        case OT_BLAZEGRAPH: {\n            try {\n                const response = await axios.get(\n                    `${tripleStoreRepositories[repository].url}/blazegraph/status`,\n                    {},\n                );\n                if (response.data !== null) {\n                    return true;\n                }\n                return false;\n            } catch (e) {\n                logger.error(`Health check failed for repository ${repository}:`, e);\n                return false;\n            }\n        }\n        case OT_FUSEKI: {\n            try {\n                const response = await axios.get(\n                    `${tripleStoreRepositories[repository].url}/$/ping`,\n                    {},\n                );\n                if (response.data !== null) {\n                    return true;\n                }\n                return false;\n            } catch (e) {\n                logger.error(`Health check failed for repository ${repository}:`, e);\n                return false;\n            }\n        }\n        case OT_GRAPHDB: {\n            const { url, username, password } = tripleStoreRepositories[repository];\n            try {\n                const response = await axios.get(\n                    `${url}/repositories/${repository}/health`,\n                    {},\n                    {\n                        auth: {\n                            username,\n                            password,\n                        },\n                    },\n                );\n                if (response.data.status === 'green') {\n                    return true;\n                }\n                return false;\n            } catch (e) {\n                if (e.response && e.response.status === 404) {\n                    // Expected error: GraphDB is up but has not created node0 repository\n                    // dkg-engine will create repo in initialization\n                    return true;\n                }\n                logger.error(`Health check failed for repository ${repository}:`, e);\n                return false;\n            }\n        }\n        default:\n            throw new Error('Invalid triple store name in healthCheck');\n    }\n}\n\nexport async function ensureConnections(tripleStoreRepositories, tripleStoreImplementation) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    const ensureConnectionPromises = Object.keys(tripleStoreRepositories).map(\n        async (repository) => {\n            let ready = await healthCheck(\n                tripleStoreRepositories,\n                repository,\n                tripleStoreImplementation,\n            );\n            let retries = 0;\n            while (!ready && retries < TRIPLE_STORE_CONNECT_MAX_RETRIES) {\n                retries += 1;\n                logger.warn(\n                    `Cannot connect to Triple store repository: ${repository}, located at: ${tripleStoreRepositories[repository].url}  retry number: ${retries}/${TRIPLE_STORE_CONNECT_MAX_RETRIES}. Retrying in ${TRIPLE_STORE_CONNECT_RETRY_FREQUENCY} seconds.`,\n                );\n                /* eslint-disable no-await-in-loop */\n                await setTimeout(TRIPLE_STORE_CONNECT_RETRY_FREQUENCY * 1000);\n                ready = await healthCheck(\n                    tripleStoreRepositories,\n                    repository,\n                    tripleStoreImplementation,\n                );\n            }\n            if (retries === TRIPLE_STORE_CONNECT_MAX_RETRIES) {\n                logger.error(\n                    `Triple Store repository: ${repository} not available, max retries reached.`,\n                );\n                process.exit(1);\n            }\n        },\n    );\n\n    await Promise.all(ensureConnectionPromises);\n}\n\n// blazegraph only\nfunction hasUnicodeCodePoints(input) {\n    const unicodeRegex = /(?<!\\\\)\\\\U([a-fA-F0-9]{8})/g;\n    return unicodeRegex.test(input);\n}\n\n// blazegraph only\nfunction decodeUnicodeCodePoints(input) {\n    const unicodeRegex = /(?<!\\\\)\\\\U([a-fA-F0-9]{8})/g;\n    const decodedString = input.replace(unicodeRegex, (match, hex) => {\n        const codePoint = parseInt(hex, 16);\n        return String.fromCodePoint(codePoint);\n    });\n\n    return decodedString;\n}\n\nexport async function _executeQuery(\n    tripleStoreRepositories,\n    repository,\n    tripleStoreImplementation,\n    query,\n    mediaType,\n) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n    validateQuery(query);\n\n    if (!mediaType) {\n        logger.error(`[VALIDATION ERROR] Media type is not defined. Media type: ${mediaType}`);\n        process.exit(1);\n    }\n\n    const response = await axios.post(\n        tripleStoreRepositories[repository].sparqlEndpoint,\n        new URLSearchParams({\n            query,\n        }),\n        {\n            headers: {\n                Accept: mediaType,\n            },\n        },\n    );\n\n    let { data } = response;\n\n    if (tripleStoreImplementation === OT_BLAZEGRAPH) {\n        // Handle Blazegraph special characters corruption\n        if (hasUnicodeCodePoints(data)) {\n            data = decodeUnicodeCodePoints(data);\n        }\n    }\n\n    return data;\n}\n\nexport async function construct(\n    tripleStoreRepositories,\n    repository,\n    tripleStoreImplementation,\n    query,\n) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n    validateQuery(query);\n\n    return _executeQuery(\n        tripleStoreRepositories,\n        repository,\n        tripleStoreImplementation,\n        query,\n        N_QUADS,\n    );\n}\n\nfunction cleanEscapeCharacter(query) {\n    return query.replace(/['|[\\]\\\\]/g, '\\\\$&');\n}\n\nexport async function getAssertion(\n    tripleStoreRepositories,\n    repository,\n    tripleStoreImplementation,\n    assertionId,\n) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n    validateAssertionId(assertionId);\n\n    const escapedGraphName = cleanEscapeCharacter(assertionId);\n    const query = `PREFIX schema: <${SCHEMA_CONTEXT}>\n                CONSTRUCT { ?s ?p ?o }\n                WHERE {\n                    {\n                        GRAPH <assertion:${escapedGraphName}>\n                        {\n                            ?s ?p ?o .\n                        }\n                    }\n                }`;\n    return construct(tripleStoreRepositories, repository, tripleStoreImplementation, query);\n}\n\nexport function extractPrivateAssertionId(publicAssertion) {\n    // Validation\n    validateAssertion(publicAssertion);\n\n    const split = publicAssertion.split(PRIVATE_ASSERTION_ONTOLOGY);\n    if (split.length <= 1 || !split[1].includes('\"') || !split[1].includes('0x')) {\n        return null;\n    }\n    const input = split[1];\n    const openingQuoteIndex = input.indexOf('\"') + 1;\n    const closingQuoteIndex = input.indexOf('\"', openingQuoteIndex);\n    const privateAssertionId = input.substring(openingQuoteIndex, closingQuoteIndex).trim();\n    if (!privateAssertionId || !privateAssertionId.includes('0x')) {\n        return null;\n    }\n    return privateAssertionId;\n}\n\nexport async function getAssertionFromV6TripleStore(\n    tripleStoreRepositories,\n    tripleStoreImplementation,\n    tokenId,\n    ualAssertionIdData,\n) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n    validateTokenId(tokenId);\n\n    if (\n        !ualAssertionIdData ||\n        typeof ualAssertionIdData !== 'object' ||\n        !ualAssertionIdData.assertionId ||\n        !ualAssertionIdData.ual\n    ) {\n        logger.error(\n            `[VALIDATION ERROR] Ual assertion ID data is not properly defined or it is not an object. Ual assertion ID data: ${ualAssertionIdData}`,\n        );\n        process.exit(1);\n    }\n\n    const { assertionId, ual } = ualAssertionIdData;\n    let success = false;\n    let publicAssertion = null;\n    let privateAssertion = null;\n\n    try {\n        // First try to fetch public data from private current repository\n        publicAssertion = await getAssertion(\n            tripleStoreRepositories,\n            PRIVATE_CURRENT,\n            tripleStoreImplementation,\n            assertionId,\n        );\n\n        // Check if public assertion is found in private current repository\n        if (publicAssertion) {\n            success = true;\n            // Check if assertion contains a private assertion\n            if (publicAssertion.includes(PRIVATE_ASSERTION_ONTOLOGY)) {\n                // Extract the private assertionId from the publicAssertion if it exists\n                const privateAssertionId = extractPrivateAssertionId(publicAssertion);\n                if (!privateAssertionId) {\n                    logger.warn(\n                        `There was a problem while extracting the private assertionId from public assertion: ${publicAssertion}. Extracted privateAssertionId: ${privateAssertionId}`,\n                    );\n                    success = false;\n                    return { tokenId, ual, publicAssertion, privateAssertion, success };\n                }\n\n                privateAssertion = await getAssertion(\n                    tripleStoreRepositories,\n                    PRIVATE_CURRENT,\n                    tripleStoreImplementation,\n                    privateAssertionId,\n                );\n\n                // If private assertionId exists but assertion could not be fetched\n                if (!privateAssertion) {\n                    logger.warn(\n                        `Private assertion with id ${privateAssertionId} could not be fetched from ${PRIVATE_CURRENT} repository even though it should exist`,\n                    );\n                }\n            }\n        } else {\n            publicAssertion = await getAssertion(\n                tripleStoreRepositories,\n                PUBLIC_CURRENT,\n                tripleStoreImplementation,\n                assertionId,\n            );\n            success = true;\n        }\n    } catch (e) {\n        logger.error(\n            `Error fetching assertion from triple store for tokenId: ${tokenId}, assertionId: ${assertionId}, error: ${e}`,\n        );\n        success = false;\n    }\n\n    return {\n        tokenId,\n        ual,\n        publicAssertion,\n        privateAssertion,\n        success,\n        assertionId,\n    };\n}\n\nexport function processContent(str) {\n    return str\n        .split('\\n')\n        .map((line) => line.trim())\n        .filter((line) => line !== '');\n}\n\nasync function ask(tripleStoreRepositories, repository, query) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateQuery(query);\n    try {\n        const response = await axios.post(\n            tripleStoreRepositories[repository].sparqlEndpoint,\n            new URLSearchParams({\n                query,\n            }),\n            {\n                headers: {\n                    Accept: 'application/json',\n                },\n            },\n        );\n\n        return response.data.boolean;\n    } catch (e) {\n        logger.error(\n            `Error while doing ASK query: ${query} in repository: ${repository}. Error: ${e.message}`,\n        );\n        return false;\n    }\n}\n\nexport async function getKnowledgeCollectionNamedGraphsExist(\n    tokenId,\n    tripleStoreRepositories,\n    knowledgeAssetUal,\n    privateAssertion,\n) {\n    const askQueries = [];\n    askQueries.push(`\n        FILTER EXISTS {\n            GRAPH <${knowledgeAssetUal}/${VISIBILITY.PUBLIC}> {\n                ?s ?p ?o\n            }\n        }\n    `);\n    if (privateAssertion) {\n        askQueries.push(`\n            FILTER EXISTS {\n                GRAPH <${knowledgeAssetUal}/${VISIBILITY.PRIVATE}> {\n                    ?s ?p ?o\n                }\n            }\n        `);\n    }\n    askQueries.push(`\n        FILTER EXISTS {\n            GRAPH <${METADATA_NAMED_GRAPH}> {\n                <${knowledgeAssetUal}> ?p ?o .\n            }\n        }\n    `);\n\n    const combinedQuery = `\n        ASK {\n            ${askQueries.join('\\n')}\n        }\n    `;\n\n    const exists = await ask(tripleStoreRepositories, DKG_REPOSITORY, combinedQuery);\n\n    return { tokenId, exists };\n}\n\nexport async function queryVoid(tripleStoreRepositories, repository, query) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateQuery(query);\n\n    await axios.post(tripleStoreRepositories[repository].sparqlEndpointUpdate, query, {\n        headers: {\n            'Content-Type': 'application/sparql-update',\n        },\n    });\n}\n\nfunction hasSpecialCharactersInIRI(assertion) {\n    const lines = assertion.split('\\n');\n    // {, }, |, ^, `, and \\ without u or U\n    // eslint-disable-next-line no-useless-escape\n    const iriPattern = /<[^>]*(?:[\\s{}\\|^`]|\\\\(?![uU]))[^>]*>/;\n\n    return lines.some((line) => {\n        // Split quad into subject, predicate, object (ignore graph if present)\n        const parts = line.trim().split(' ');\n\n        // Check each part only if it starts with < and ends with >\n        return parts.some((part) => {\n            if (part.startsWith('<') && part.endsWith('>')) {\n                return iriPattern.test(part);\n            }\n            return false;\n        });\n    });\n}\n\nexport async function insertAssertionsIntoV8UnifiedRepository(\n    v6Assertions,\n    tripleStoreRepositories,\n) {\n    // Insert into new repository\n    const successfullyProcessed = [];\n    const assertionsToCheck = [];\n    const insertQueries = [];\n    for (const assertion of v6Assertions) {\n        const { tokenId, ual, publicAssertion, privateAssertion } = assertion;\n\n        // Assertion with assertionId does not exist in triple store. Continue\n        if (!publicAssertion) {\n            successfullyProcessed.push(tokenId);\n            continue;\n        }\n\n        if (hasSpecialCharactersInIRI(publicAssertion)) {\n            logger.warn(\n                `Public assertion with tokenId: ${tokenId} contains illegal characters in IRI. Skipping...\n                Public assertion: ${publicAssertion}`,\n            );\n            successfullyProcessed.push(tokenId);\n            continue;\n        }\n\n        const knowledgeAssetUal = `${ual.toLowerCase()}/1`;\n\n        const publicNQuads = processContent(publicAssertion);\n        insertQueries.push(`\n            GRAPH <${knowledgeAssetUal}/${VISIBILITY.PUBLIC}> {\n                ${publicNQuads.join('\\n')}\n            }\n        `);\n\n        if (privateAssertion) {\n            const privateNQuads = processContent(privateAssertion);\n            insertQueries.push(`\n                GRAPH <${knowledgeAssetUal}/${VISIBILITY.PRIVATE}> {\n                    ${privateNQuads.join('\\n')}\n                }\n            `);\n        }\n\n        const metadataNQuads = `<${knowledgeAssetUal}> <http://schema.org/states> \"${knowledgeAssetUal}:0\" .`;\n        insertQueries.push(`\n                GRAPH <${METADATA_NAMED_GRAPH}> {\n                ${metadataNQuads}\n            }\n        `);\n\n        assertionsToCheck.push(assertion);\n    }\n\n    if (insertQueries.length > 0) {\n        const combinedQuery = `\n                PREFIX schema: <${SCHEMA_CONTEXT}>\n                INSERT DATA {\n                    ${insertQueries.join('\\n')}\n                }\n            `;\n\n        logger.time(`INSERTING ${assertionsToCheck.length} ASSERTIONS INTO V8 TRIPLE STORE`);\n        await queryVoid(tripleStoreRepositories, DKG_REPOSITORY, combinedQuery);\n        logger.timeEnd(`INSERTING ${assertionsToCheck.length} ASSERTIONS INTO V8 TRIPLE STORE`);\n    }\n\n    return { successfullyProcessed, assertionsToCheck };\n}\n\nexport async function knowledgeCollectionNamedGraphExists(\n    tripleStoreRepositories,\n    repository,\n    ual,\n    visibility,\n) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateUal(ual);\n    if (!visibility || !Object.values(VISIBILITY).includes(visibility)) {\n        throw new Error(\n            `Visibility is not defined or it is not a valid visibility. Visibility: ${visibility}`,\n        );\n    }\n\n    const query = `\n        ASK {\n            GRAPH ?g {\n                ?s ?p ?o\n            }\n            FILTER(STRSTARTS(STR(?g), \"${ual}/${visibility}\"))\n        }\n    `;\n\n    return ask(tripleStoreRepositories, repository, query);\n}\n\nexport async function knowledgeAssetMetadataExists(tripleStoreRepositories, repository, ual) {\n    // Validation\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateRepository(repository);\n    validateUal(ual);\n\n    const query = `\n        ASK {\n            GRAPH <${METADATA_NAMED_GRAPH}> {\n                <${ual}> ?p ?o .\n            }\n        }\n    `;\n\n    return ask(tripleStoreRepositories, repository, query);\n}\n"
  },
  {
    "path": "v8-data-migration/v8-data-migration-utils.js",
    "content": "import fs from 'fs';\nimport path from 'path';\nimport {\n    NODERC_CONFIG_PATH,\n    MIGRATION_PROGRESS_FILE,\n    DEFAULT_CONFIG_PATH,\n    MIGRATION_DIR,\n} from './constants.js';\nimport { validateConfig } from './validation.js';\nimport logger from './logger.js';\n\nexport function initializeConfig() {\n    const configPath = path.resolve(NODERC_CONFIG_PATH);\n    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n    validateConfig(config);\n    return config;\n}\n\nexport function initializeDefaultConfig() {\n    const configPath = path.resolve(DEFAULT_CONFIG_PATH);\n    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));\n    validateConfig(config);\n    return config;\n}\n\nexport function ensureDirectoryExists(dirPath) {\n    if (!fs.existsSync(dirPath)) {\n        fs.mkdirSync(dirPath, { recursive: true });\n        logger.info(`Created directory: ${dirPath}`);\n\n        if (!fs.existsSync(dirPath)) {\n            logger.error(\n                `Something went wrong. Directory: ${dirPath} does not exist after creation.`,\n            );\n            process.exit(1);\n        }\n    }\n}\n\nexport function ensureMigrationProgressFileExists() {\n    ensureDirectoryExists(MIGRATION_DIR);\n    const migrationProgressFilePath = path.join(MIGRATION_DIR, MIGRATION_PROGRESS_FILE);\n\n    if (!fs.existsSync(migrationProgressFilePath)) {\n        fs.writeFileSync(migrationProgressFilePath, '');\n        logger.info(`Created migration progress file: ${migrationProgressFilePath}`);\n        if (!fs.existsSync(migrationProgressFilePath)) {\n            throw new Error(\n                `Something went wrong. Progress file: ${migrationProgressFilePath} does not exist after creation.`,\n            );\n        }\n    } else {\n        logger.info(`Migration progress file already exists: ${migrationProgressFilePath}.`);\n        logger.info('Checking if migration is already successful...');\n        const fileContent = fs.readFileSync(migrationProgressFilePath, 'utf8');\n        if (fileContent === 'MIGRATED') {\n            logger.info('Migration is already successful. Exiting...');\n            process.exit(0);\n        }\n    }\n}\n\nexport function markMigrationAsSuccessfull() {\n    // Construct the full path to the migration progress file\n    const migrationProgressFilePath = path.join(MIGRATION_DIR, MIGRATION_PROGRESS_FILE);\n\n    // open file\n    const file = fs.openSync(migrationProgressFilePath, 'w');\n\n    // write MIGRATED\n    fs.writeSync(file, 'MIGRATED');\n\n    // close file\n    fs.closeSync(file);\n}\n\nexport function deleteFile(filePath) {\n    if (fs.existsSync(filePath)) {\n        fs.unlinkSync(filePath);\n        logger.info(`Deleted file: ${filePath}`);\n\n        if (fs.existsSync(filePath)) {\n            logger.error(`File: ${filePath} still exists after deletion.`);\n            process.exit(1);\n        }\n    } else {\n        logger.info(`Did not delete file: ${filePath} because it does not exist.`);\n    }\n}\n"
  },
  {
    "path": "v8-data-migration/v8-data-migration.js",
    "content": "import path from 'path';\nimport fs from 'fs';\nimport { createRequire } from 'module';\nimport dotenv from 'dotenv';\nimport axios from 'axios';\nimport { BATCH_SIZE, ENV_PATH, BLOCKCHAINS, DATA_MIGRATION_DIR, DB_URLS } from './constants.js';\nimport {\n    initializeConfig,\n    initializeDefaultConfig,\n    ensureDirectoryExists,\n    ensureMigrationProgressFileExists,\n    markMigrationAsSuccessfull,\n    deleteFile,\n} from './v8-data-migration-utils.js';\nimport {\n    getAssertionFromV6TripleStore,\n    insertAssertionsIntoV8UnifiedRepository,\n    getTripleStoreData,\n    initializeRepositories,\n    initializeContexts,\n    ensureConnections,\n    getKnowledgeCollectionNamedGraphsExist,\n} from './triple-store-utils.js';\nimport { getContentAssetStorageContract, initializeRpc } from './blockchain-utils.js';\nimport {\n    validateBlockchainName,\n    validateBlockchainDetails,\n    validateTokenId,\n    validateTripleStoreRepositories,\n    validateTripleStoreImplementation,\n    validateBatchData,\n} from './validation.js';\nimport sqliteDb from './sqlite-utils.js';\nimport logger from './logger.js';\n\ndotenv.config({ path: ENV_PATH, override: true });\n\nconst require = createRequire(import.meta.url);\nconst { setTimeout } = require('timers/promises');\n\nconst successfulInsertsSet = new Set();\nconst totalInsertsSet = new Set();\n\nasync function processAndInsertNewerAssertions(\n    blockchainDetails,\n    blockchainName,\n    highestTokenId,\n    tripleStoreRepositories,\n    tripleStoreImplementation,\n    rpcEndpoints,\n) {\n    // Validation\n    validateBlockchainName(blockchainName);\n    validateBlockchainDetails(blockchainDetails);\n    validateTokenId(highestTokenId);\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    const provider = await initializeRpc(rpcEndpoints[0]);\n    const storageContract = await getContentAssetStorageContract(provider, blockchainDetails);\n    let assertionExists = true;\n    let newTokenId = highestTokenId;\n\n    /* eslint-disable no-await-in-loop */\n    while (assertionExists) {\n        // increase the tokenId by 1\n        newTokenId += 1;\n        logger.info(`Fetching assertion for tokenId: ${newTokenId}`);\n\n        // construct new ual\n        const newUal = `did:dkg:${blockchainDetails.ID}/${blockchainDetails.CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS}/${newTokenId}`;\n\n        const assertionIds = await storageContract.getAssertionIds(newTokenId);\n        if (assertionIds.length === 0) {\n            logger.info(\n                `You have processed all assertions on ${blockchainName}. Moving to the next blockchain...`,\n            );\n            assertionExists = false;\n            break;\n        }\n\n        // Get the latest assertionId\n        const assertionId = assertionIds[assertionIds.length - 1];\n\n        const assertion = await getAssertionFromV6TripleStore(\n            tripleStoreRepositories,\n            tripleStoreImplementation,\n            newTokenId,\n            {\n                assertionId,\n                ual: newUal,\n            },\n        );\n\n        if (!assertion.success) {\n            logger.error(\n                `Assertion with assertionId ${assertionId} exists in V6 triple store but could not be fetched. Retrying...`,\n            );\n            newTokenId -= 1;\n            continue;\n        }\n\n        const { successfullyProcessed, assertionsToCheck } =\n            await insertAssertionsIntoV8UnifiedRepository([assertion], tripleStoreRepositories);\n\n        if (successfullyProcessed.length > 0) {\n            logger.info(\n                `Assertion with assertionId ${assertionId} does not exist in V6 triple store.`,\n            );\n        }\n\n        if (assertionsToCheck.length > 0) {\n            const { tokenId, ual, privateAssertion } = assertionsToCheck[0];\n            const knowledgeAssetUal = `${ual.toLowerCase()}/1`;\n            logger.time(`GETTING KNOWLEDGE COLLECTION NAMED GRAPHS EXIST FOR 1 ASSERTION`);\n            // eslint-disable-next-line no-await-in-loop\n            const { exists } = await getKnowledgeCollectionNamedGraphsExist(\n                tokenId,\n                tripleStoreRepositories,\n                knowledgeAssetUal,\n                privateAssertion,\n            );\n            logger.timeEnd(`GETTING KNOWLEDGE COLLECTION NAMED GRAPHS EXIST FOR 1 ASSERTION`);\n\n            if (!exists) {\n                logger.error(\n                    `Assertion with assertionId ${assertionId} was inserted but its KA named graph does not exist. Retrying...`,\n                );\n                newTokenId -= 1;\n                continue;\n            }\n\n            logger.info(\n                `Successfully inserted public/private assertions into V8 triple store for tokenId: ${newTokenId}`,\n            );\n\n            successfulInsertsSet.add(assertionId);\n            totalInsertsSet.add(newTokenId);\n        }\n\n        const inserted = await sqliteDb.insertAssertion(\n            blockchainName,\n            newTokenId,\n            newUal,\n            assertionId,\n        );\n        if (!inserted) {\n            logger.error(\n                `Assertion with assertionId ${assertionId} could not be inserted. Retrying...`,\n            );\n            newTokenId -= 1;\n            continue;\n        }\n        logger.info(\n            `Assertion with tokenId ${newTokenId} inserted into db and marked as processed.`,\n        );\n    }\n}\n\nasync function processAndInsertAssertions(\n    v6Assertions,\n    tripleStoreRepositories,\n    tripleStoreImplementation,\n) {\n    // Validation\n    if (!v6Assertions || !Array.isArray(v6Assertions)) {\n        throw new Error(\n            `v6Assertions is not defined or it is not an array. V6 assertions: ${v6Assertions}`,\n        );\n    }\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    const { successfullyProcessed, assertionsToCheck } =\n        await insertAssertionsIntoV8UnifiedRepository(v6Assertions, tripleStoreRepositories);\n\n    logger.info(\n        `Number of assertions that do not exist in V6 triple store: ${successfullyProcessed.length}`,\n    );\n\n    logger.info(`Verifying V8 triple store insertions for ${assertionsToCheck.length} assertions`);\n\n    const promises = [];\n    for (const assertion of assertionsToCheck) {\n        const { tokenId, ual, privateAssertion } = assertion;\n        const knowledgeAssetUal = `${ual.toLowerCase()}/1`;\n\n        promises.push(\n            getKnowledgeCollectionNamedGraphsExist(\n                tokenId,\n                tripleStoreRepositories,\n                knowledgeAssetUal,\n                privateAssertion,\n            ),\n        );\n    }\n\n    logger.time(\n        `GETTING KNOWLEDGE COLLECTION NAMED GRAPHS EXIST FOR ${promises.length} ASSERTIONS`,\n    );\n    const results = await Promise.all(promises);\n    logger.timeEnd(\n        `GETTING KNOWLEDGE COLLECTION NAMED GRAPHS EXIST FOR ${promises.length} ASSERTIONS`,\n    );\n\n    const successfulInserts = results\n        .filter((result) => result.exists)\n        .map((result) => result.tokenId);\n\n    // Find the assertion associated with the tokenId\n    successfulInserts.forEach((tokenId) => {\n        const assertionInserted = assertionsToCheck.find(\n            (assertion) => assertion.tokenId === tokenId,\n        );\n        if (assertionInserted) {\n            successfulInsertsSet.add(assertionInserted.assertionId);\n        }\n    });\n\n    successfulInserts.forEach((tokenId) => {\n        totalInsertsSet.add(tokenId);\n    });\n\n    logger.info(`Number of successfully inserted assertions: ${successfulInserts.length}`);\n\n    successfullyProcessed.push(...successfulInserts);\n    logger.info(`Successfully processed assertions: ${successfullyProcessed.length}`);\n\n    return successfullyProcessed;\n}\n\nasync function getAssertionsInBatch(\n    batchKeys,\n    batchData,\n    tripleStoreRepositories,\n    tripleStoreImplementation,\n) {\n    // Validation\n    if (!batchKeys || !Array.isArray(batchKeys)) {\n        logger.error(`Batch keys is not defined or it is not an array. Batch keys: ${batchKeys}`);\n        process.exit(1);\n    }\n    validateBatchData(batchData);\n    validateTripleStoreRepositories(tripleStoreRepositories);\n    validateTripleStoreImplementation(tripleStoreImplementation);\n\n    const batchPromises = [];\n    for (const tokenId of batchKeys) {\n        batchPromises.push(\n            getAssertionFromV6TripleStore(\n                tripleStoreRepositories,\n                tripleStoreImplementation,\n                tokenId,\n                batchData[tokenId],\n            ),\n        );\n    }\n\n    const batchResults = await Promise.all(batchPromises);\n\n    // Get all successful assertions\n    const v6Assertions = batchResults.filter((result) => result.success);\n\n    return v6Assertions;\n}\n\nasync function downloadDb(dbFilePath) {\n    logger.time(`Database file downloading time`);\n    const maxAttempts = 3;\n    for (let i = 0; i < maxAttempts; i += 1) {\n        // Fetch the db file from the remote server\n        logger.info(\n            `Fetching ${process.env.NODE_ENV}.db file from ${DB_URLS[process.env.NODE_ENV]}. Try ${\n                i + 1\n            } of 3. This may take a while...`,\n        );\n\n        try {\n            const writer = fs.createWriteStream(dbFilePath);\n            const response = await axios({\n                url: DB_URLS[process.env.NODE_ENV],\n                method: 'GET',\n                responseType: 'stream',\n            });\n\n            // Pipe the response stream to the file\n            response.data.pipe(writer);\n\n            await new Promise((resolve, reject) => {\n                let downloadComplete = false;\n\n                response.data.on('end', () => {\n                    downloadComplete = true;\n                });\n\n                writer.on('finish', resolve);\n                writer.on('error', (err) =>\n                    reject(new Error(`Write stream error: ${err.message}`)),\n                );\n                response.data.on('error', (err) =>\n                    reject(new Error(`Download stream error: ${err.message}`)),\n                );\n                response.data.on('close', () => {\n                    if (!downloadComplete) {\n                        reject(new Error('Download stream closed before completing'));\n                    }\n                });\n            });\n            if (fs.existsSync(dbFilePath)) {\n                logger.info(`DB file downloaded successfully`);\n                break;\n            }\n            logger.error(`DB file for ${process.env.NODE_ENV} is not present after download.`);\n        } catch (error) {\n            logger.error(`Error downloading DB file: ${error.message}`);\n        }\n\n        logger.info('Deleting downloaded db file to prevent data corruption');\n        deleteFile(dbFilePath);\n\n        if (i === maxAttempts - 1) {\n            logger.error('Max db download attempts reached. Terminating process...');\n            process.exit(1);\n        }\n        logger.info(`Retrying db download...`);\n    }\n    logger.timeEnd(`Database file downloading time`);\n}\n\nasync function main() {\n    ensureMigrationProgressFileExists();\n\n    // Make sure data/data-migration directory exists\n    ensureDirectoryExists(DATA_MIGRATION_DIR);\n\n    // initialize noderc config\n    const config = initializeConfig();\n\n    // initialize default config\n    const defaultConfig = initializeDefaultConfig();\n\n    // Initialize blockchain config\n    const blockchainConfig = config.modules.blockchain;\n    if (!blockchainConfig || !blockchainConfig.implementation) {\n        logger.error('Invalid configuration for blockchain.');\n        process.exit(1);\n    }\n\n    logger.info('TRIPLE STORE INITIALIZATION START');\n\n    // Initialize triple store config\n    const tripleStoreConfig = config.modules.tripleStore;\n    if (!tripleStoreConfig || !tripleStoreConfig.implementation) {\n        logger.error('Invalid configuration for triple store.');\n        process.exit(1);\n    }\n\n    const tripleStoreData = getTripleStoreData(tripleStoreConfig);\n    // eslint-disable-next-line prefer-destructuring\n    const tripleStoreImplementation = tripleStoreData.tripleStoreImplementation;\n    // eslint-disable-next-line prefer-destructuring\n    let tripleStoreRepositories = tripleStoreData.tripleStoreRepositories;\n\n    if (Object.keys(tripleStoreRepositories).length !== 3) {\n        logger.error(\n            `Triple store repositories are not initialized correctly. Expected 3 repositories, got: ${\n                Object.keys(tripleStoreRepositories).length\n            }`,\n        );\n        process.exit(1);\n    }\n\n    // Initialize repositories\n    tripleStoreRepositories = initializeRepositories(\n        tripleStoreRepositories,\n        tripleStoreImplementation,\n    );\n\n    // Initialize contexts\n    tripleStoreRepositories = initializeContexts(tripleStoreRepositories);\n\n    // Ensure connections\n    await ensureConnections(tripleStoreRepositories, tripleStoreImplementation);\n\n    const maxAttempts = 2;\n    for (let i = 0; i < maxAttempts; i += 1) {\n        // Check if db exists and if it doesn't download it to the relevant directory\n        const dbFilePath = path.join(DATA_MIGRATION_DIR, `${process.env.NODE_ENV}.db`);\n        if (!fs.existsSync(dbFilePath)) {\n            logger.info(\n                `DB file for ${process.env.NODE_ENV} does not exist in ${DATA_MIGRATION_DIR}. Downloading it...`,\n            );\n            await downloadDb(dbFilePath);\n        }\n\n        logger.info('Initializing SQLite database');\n        await sqliteDb.initialize();\n\n        // Check if db is corrupted and handle accordingly\n        const integrityCheck = await sqliteDb.checkIntegrity();\n        if (!integrityCheck) {\n            await sqliteDb.close();\n            logger.info('Db integrity check failed. Deleting corrupt db file.');\n            deleteFile(dbFilePath);\n\n            if (i === maxAttempts - 1) {\n                logger.error('Db integrity check failed. Terminating process...');\n                process.exit(1);\n            }\n            logger.info(`Retrying db download and integrity check...`);\n            continue;\n        }\n        break;\n    }\n\n    try {\n        // make sure blockchains are always migrated in this order - base, gnosis, neuroweb\n        const sortedBlockchains = Object.keys(blockchainConfig.implementation).sort();\n        // Iterate through all chains\n        for (const blockchain of sortedBlockchains) {\n            logger.time(`PROCESSING TIME FOR ${blockchain}`);\n            let processed = 0;\n            const blockchainImplementation = blockchainConfig.implementation[blockchain];\n            if (!blockchainImplementation.enabled) {\n                logger.info(`Blockchain ${blockchain} is not enabled. Skipping...`);\n                continue;\n            }\n            const rpcEndpoints = blockchainImplementation?.config?.rpcEndpoints\n                ? blockchainImplementation.config.rpcEndpoints\n                : defaultConfig[process.env.NODE_ENV].modules.blockchain.implementation[blockchain]\n                      .config.rpcEndpoints;\n            if (!Array.isArray(rpcEndpoints) || rpcEndpoints.length === 0) {\n                logger.error(`RPC endpoints are not defined for blockchain ${blockchain}.`);\n                process.exit(1);\n            }\n\n            let blockchainName;\n            let blockchainDetails;\n            for (const [, details] of Object.entries(BLOCKCHAINS)) {\n                if (details.ID === blockchain && details.ENV === process.env.NODE_ENV) {\n                    blockchainName = details.NAME;\n                    blockchainDetails = details;\n                    break;\n                }\n            }\n\n            if (!blockchainName) {\n                logger.error(\n                    `Blockchain ${blockchain} not found. Make sure you have the correct blockchain ID and correct NODE_ENV in .env file.`,\n                );\n                process.exit(1);\n            }\n\n            const tableExists = await sqliteDb.getTableExists(blockchainName);\n\n            if (!tableExists) {\n                logger.error(`Required table \"${blockchainName}\" does not exist in the database`);\n                process.exit(1);\n            }\n\n            const highestTokenId = await sqliteDb.getHighestTokenId(blockchainName);\n            if (!highestTokenId) {\n                logger.error(\n                    `Something went wrong. Could not fetch highest tokenId for ${blockchainName}.`,\n                );\n                process.exit(1);\n            }\n            logger.info(`Total amount of tokenIds: ${highestTokenId}`);\n\n            const tokenIdsToProcessCount = await sqliteDb.getUnprocessedCount(blockchainName);\n            logger.info(`Amount of tokenIds left to process: ${tokenIdsToProcessCount}`);\n\n            // Process tokens in batches\n            while (true) {\n                logger.time('BATCH PROCESSING TIME');\n\n                const batchData = await sqliteDb.getBatchOfUnprocessedTokenIds(\n                    blockchainName,\n                    BATCH_SIZE,\n                );\n                const batchKeys = Object.keys(batchData);\n\n                if (batchKeys.length === 0) {\n                    logger.info('No more unprocessed tokenIds found. Moving on...');\n                    logger.timeEnd('BATCH PROCESSING TIME');\n                    break;\n                }\n\n                logger.info(`Processing batch: ${batchKeys}`);\n\n                try {\n                    logger.time('FETCHING V6 ASSERTIONS');\n                    const v6Assertions = await getAssertionsInBatch(\n                        batchKeys,\n                        batchData,\n                        tripleStoreRepositories,\n                        tripleStoreImplementation,\n                    );\n                    logger.timeEnd('FETCHING V6 ASSERTIONS');\n\n                    if (v6Assertions.length === 0) {\n                        throw new Error(\n                            `Something went wrong. Could not get any V6 assertions in batch ${batchKeys}`,\n                        );\n                    }\n\n                    logger.info(`Number of V6 assertions to process: ${v6Assertions.length}`);\n\n                    const successfullyProcessed = await processAndInsertAssertions(\n                        v6Assertions,\n                        tripleStoreRepositories,\n                        tripleStoreImplementation,\n                    );\n\n                    if (successfullyProcessed.length === 0) {\n                        throw new Error(\n                            `Could not insert any assertions out of ${v6Assertions.length}`,\n                        );\n                    }\n\n                    logger.info(\n                        `Successfully processed/inserted assertions: ${successfullyProcessed.length}. Marking rows as processed in db...`,\n                    );\n\n                    await sqliteDb.markRowsAsProcessed(blockchainName, successfullyProcessed);\n                    processed += successfullyProcessed.length;\n\n                    logger.info(\n                        `[PROGRESS] for ${blockchainName}: ${(\n                            (processed / tokenIdsToProcessCount) *\n                            100\n                        ).toFixed(2)}%. Total processed: ${processed}/${tokenIdsToProcessCount}`,\n                    );\n\n                    // Pause for 500ms to deload the triple store\n                    await setTimeout(500);\n                } catch (error) {\n                    logger.error(`Error processing batch: ${error}. Pausing for 5 seconds...`);\n                    await setTimeout(5000);\n                }\n            }\n\n            logger.timeEnd(`PROCESSING TIME FOR ${blockchain}`);\n\n            logger.time(`PROCESS AND INSERT NEWER ASSERTIONS FOR ${blockchainName}`);\n            // If newer (unprocessed) assertions exist on-chain, fetch them and insert them into the V8 triple store repository\n            // eslint-disable-next-line no-await-in-loop\n            await processAndInsertNewerAssertions(\n                blockchainDetails,\n                blockchainName,\n                highestTokenId,\n                tripleStoreRepositories,\n                tripleStoreImplementation,\n                rpcEndpoints,\n            );\n            logger.timeEnd(`PROCESS AND INSERT NEWER ASSERTIONS FOR ${blockchainName}`);\n\n            logger.info(\n                `Total amount of unique successfully inserted assertions for ${blockchainName}: ${successfulInsertsSet.size}`,\n            );\n            logger.info(\n                `Total amount of assertions inserted for ${blockchainName}: ${totalInsertsSet.size}`,\n            );\n        }\n    } finally {\n        // Close database connection after all blockchains are processed\n        await sqliteDb.close();\n    }\n\n    markMigrationAsSuccessfull();\n\n    logger.info(\n        `Total amount of unique successfully inserted assertions: ${successfulInsertsSet.size}`,\n    );\n    logger.info(`Total amount of assertions inserted: ${totalInsertsSet.size}`);\n}\n\nmain();\n"
  },
  {
    "path": "v8-data-migration/validation.js",
    "content": "import logger from './logger.js';\n\nexport function validateConfig(config) {\n    if (!config || typeof config !== 'object') {\n        logger.error(\n            `[VALIDATION ERROR] Config is not defined or it is not an object. Config: ${config}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateBlockchainName(blockchainName) {\n    if (!blockchainName || typeof blockchainName !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Blockchain name is defined or it is not a string. Blockchain name: ${blockchainName}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateBlockchainDetails(blockchainDetails) {\n    if (\n        !blockchainDetails ||\n        typeof blockchainDetails !== 'object' ||\n        !Object.keys(blockchainDetails).includes('ID') ||\n        !Object.keys(blockchainDetails).includes('ENV') ||\n        !Object.keys(blockchainDetails).includes('NAME') ||\n        !Object.keys(blockchainDetails).includes('CONTENT_ASSET_STORAGE_CONTRACT_ADDRESS')\n    ) {\n        logger.error(\n            `[VALIDATION ERROR] Blockchain details is defined or it is not an object. Blockchain details: ${blockchainDetails}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateTokenId(tokenId) {\n    if (typeof tokenId !== 'string' && typeof tokenId !== 'number') {\n        logger.error(\n            `[VALIDATION ERROR] Token ID is not a string or number. Token ID: ${tokenId}. Type: ${typeof tokenId}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateUal(ual) {\n    if (!ual.startsWith('did:dkg:') || typeof ual !== 'string') {\n        logger.error(`[VALIDATION ERROR] UAL is not a valid UAL. UAL: ${ual}`);\n        process.exit(1);\n    }\n}\n\nexport function validateTripleStoreRepositories(tripleStoreRepositories) {\n    if (!tripleStoreRepositories || typeof tripleStoreRepositories !== 'object') {\n        logger.error(\n            `[VALIDATION ERROR] Triple store repositories is not defined or it is not an object. Triple store repositories: ${tripleStoreRepositories}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateTripleStoreImplementation(tripleStoreImplementation) {\n    if (!tripleStoreImplementation || typeof tripleStoreImplementation !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Triple store implementation is not defined or it is not a string. Triple store implementation: ${tripleStoreImplementation}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateTripleStoreConfig(tripleStoreConfig) {\n    if (!tripleStoreConfig || typeof tripleStoreConfig !== 'object') {\n        logger.error(\n            `[VALIDATION ERROR] Triple store config is not defined or it is not an object. Triple store config: ${tripleStoreConfig}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateRepository(repository) {\n    if (!repository || typeof repository !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Repository is not defined or it is not a string. Repository: ${repository}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateQuery(query) {\n    if (!query || typeof query !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Query is not defined or it is not a string. Query: ${query}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateAssertionId(assertionId) {\n    if (!assertionId || typeof assertionId !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Assertion ID is not defined or it is not a string. Assertion ID: ${assertionId}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateAssertion(assertion) {\n    if (!assertion || typeof assertion !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Assertion is not defined or it is not a string. Assertion: ${assertion}`,\n        );\n        process.exit(1);\n    }\n}\n\n// BLOCKCHAIN\nexport function validateProvider(provider) {\n    if (!provider || typeof provider !== 'object') {\n        logger.error(\n            `[VALIDATION ERROR] Provider is not defined or it is not an object. Provider: ${provider}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateStorageContractAddress(storageContractAddress) {\n    if (!storageContractAddress || typeof storageContractAddress !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Storage contract address is not defined or it is not a string. Storage contract address: ${storageContractAddress}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateStorageContractName(storageContractName) {\n    if (!storageContractName || typeof storageContractName !== 'string') {\n        logger.error(\n            `[VALIDATION ERROR] Storage contract name is not defined or it is not a string. Storage contract name: ${storageContractName}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateStorageContractAbi(storageContractAbi) {\n    if (!storageContractAbi || typeof storageContractAbi !== 'object') {\n        logger.error(\n            `[VALIDATION ERROR] Storage contract ABI is not defined or it is not an object. Storage contract ABI: ${storageContractAbi}`,\n        );\n        process.exit(1);\n    }\n}\n\nexport function validateBatchData(batchData) {\n    if (!batchData || typeof batchData !== 'object') {\n        logger.error(\n            `[VALIDATION ERROR] Batch data is not defined or it is not an object. Batch data: ${batchData}`,\n        );\n        process.exit(1);\n    }\n}\n"
  }
]