├── .dockerignore ├── .editorconfig ├── .github └── workflows │ ├── docker.yml │ └── test.yml ├── .gitignore ├── .mocharc.json ├── .nycrc.json ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── apidoc-template ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── img │ ├── favicon-128x128.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-64x64.png │ └── favicon.ico ├── index.html ├── package.json └── src │ ├── css │ └── main.css │ ├── diff_match_patch.mjs │ ├── hb_helpers.js │ ├── jsonifier.mjs │ ├── locales │ ├── ca.mjs │ ├── cs.mjs │ ├── de.mjs │ ├── es.mjs │ ├── fr.mjs │ ├── it.mjs │ ├── locale.mjs │ ├── nl.mjs │ ├── pl.mjs │ ├── pt_br.mjs │ ├── ro.mjs │ ├── ru.mjs │ ├── tr.mjs │ ├── vi.mjs │ └── zh_cn.mjs │ ├── main.js │ ├── sampreq_url_processor.mjs │ ├── send_sample_request.js │ └── webpack.config.js ├── casket_example.casket ├── eslint.config.mjs ├── package.json ├── pnpm-lock.yaml ├── src ├── controllers │ ├── addresses.ts │ ├── blocks.ts │ ├── names.ts │ └── transactions.ts ├── database │ ├── index.ts │ ├── migrations │ │ ├── 2024-06-27T17-58-00-init.ts │ │ ├── 2024-06-27T17-59-00-cleanup.ts │ │ ├── 2024-06-27T19-07-00-add-request-id.ts │ │ └── barrel.ts │ ├── redis.ts │ └── schemas.ts ├── docs.ts ├── errors │ ├── KristError.ts │ ├── addresses.ts │ ├── blocks.ts │ ├── generic.ts │ ├── index.ts │ ├── names.ts │ ├── sendErrors.ts │ ├── transactions.ts │ ├── webserver.ts │ └── websockets.ts ├── index.ts ├── krist │ ├── addresses │ │ ├── index.ts │ │ ├── lookup.ts │ │ └── verify.ts │ ├── authLog.ts │ ├── blocks │ │ ├── index.ts │ │ ├── lookup.ts │ │ └── submit.ts │ ├── index.ts │ ├── motd.ts │ ├── names │ │ ├── index.ts │ │ └── lookup.ts │ ├── supply.ts │ ├── switches.ts │ ├── transactions │ │ ├── create.ts │ │ ├── index.ts │ │ └── lookup.ts │ └── work.ts ├── typings │ └── why-is-node-running.d.ts ├── utils │ ├── baseBlockValue.ts │ ├── checkEnv.ts │ ├── criticalLog.ts │ ├── crypto.ts │ ├── fileExists.ts │ ├── format.ts │ ├── git.ts │ ├── index.ts │ ├── legacyWork.ts │ ├── log.ts │ ├── lut.ts │ ├── rateLimit.ts │ ├── validation.ts │ ├── validationKrist.ts │ ├── vars.ts │ └── whatsNew.ts ├── webserver │ ├── idempotency.ts │ ├── index.ts │ ├── prometheus.ts │ ├── routes │ │ ├── addresses.ts │ │ ├── blocks.ts │ │ ├── homepage.ts │ │ ├── index.ts │ │ ├── login.ts │ │ ├── lookup │ │ │ ├── addresses.ts │ │ │ ├── blocks.ts │ │ │ ├── index.ts │ │ │ ├── names.ts │ │ │ ├── transactions.ts │ │ │ └── utils.ts │ │ ├── motd.ts │ │ ├── names.ts │ │ ├── search │ │ │ ├── index.ts │ │ │ ├── search.ts │ │ │ ├── searchExtended.ts │ │ │ └── utils.ts │ │ ├── submission.ts │ │ ├── supply.ts │ │ ├── transactions.ts │ │ ├── v2.ts │ │ ├── walletVersion.ts │ │ ├── websockets.ts │ │ ├── whatsNew.ts │ │ └── work.ts │ └── utils.ts └── websockets │ ├── WebSocketManager.ts │ ├── WrappedWebSocket.ts │ ├── index.ts │ ├── ipc.ts │ ├── prometheus.ts │ ├── routes │ ├── addresses.ts │ ├── index.ts │ ├── login.ts │ ├── logout.ts │ ├── me.ts │ ├── submission.ts │ ├── subscription.ts │ ├── transactions.ts │ └── work.ts │ ├── subscriptionCheck.ts │ └── types.ts ├── static ├── docs.bundle.js ├── down.html ├── favicon-128x128.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-64x64.png ├── favicon.ico ├── logo2.svg └── style.css ├── test ├── api.ts ├── fixtures.ts ├── misc │ ├── json.test.ts │ ├── krist.test.ts │ └── utils.test.ts ├── routes │ ├── addresses.test.ts │ ├── blocks.test.ts │ ├── login.test.ts │ ├── lookup.test.ts │ ├── motd.test.ts │ ├── names.test.ts │ ├── submission.test.ts │ ├── transactions.test.ts │ ├── websockets.test.ts │ └── work.test.ts ├── seed.ts ├── websocket_routes │ ├── addresses.test.ts │ ├── me.test.ts │ ├── submission.test.ts │ └── transactions.test.ts └── ws.ts ├── tsconfig.json ├── tsconfig.test.json ├── views ├── error_404.hbs ├── error_500.hbs ├── home.hbs ├── layouts │ └── main.hbs └── partials │ ├── link.hbs │ └── new.hbs └── whats-new.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .env 4 | dist 5 | coverage 6 | casket_example.casket 7 | mochawesome-report 8 | .nyc_output 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.{js,cjs,mjs,jsx,ts,tsx,css,scss,json,swig,html,hbs,md}] 9 | quote_type = double 10 | indent_style = space 11 | indent_size = 2 12 | trim_trailing_whitespace = true 13 | max_line_length = 120 -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker Image 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | push_to_registry: 8 | name: Push Docker image to GitHub Container Registry 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out the repo 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 10 15 | 16 | - name: Add Docker tags 17 | id: docker_meta 18 | uses: docker/metadata-action@v5 19 | with: 20 | images: ghcr.io/${{ github.repository }} 21 | 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v3 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v3 27 | 28 | - name: Login to GitHub Container Registry 29 | uses: docker/login-action@v3 30 | with: 31 | registry: ghcr.io 32 | username: ${{ secrets.GHCR_USERNAME }} 33 | password: ${{ secrets.GHCR_PAT }} 34 | 35 | - name: Push to GitHub Container Registry 36 | uses: docker/build-push-action@v5 37 | with: 38 | context: . 39 | file: ./Dockerfile 40 | push: true 41 | tags: ${{ steps.docker_meta.outputs.tags }} 42 | labels: ${{ steps.docker_meta.outputs.labels }} 43 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test Krist 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | container: node:20 9 | 10 | services: 11 | mariadb: 12 | image: mariadb:10.4 13 | env: 14 | MYSQL_USER: test_krist 15 | MYSQL_DATABASE: test_krist 16 | MYSQL_PASSWORD: test_krist 17 | MYSQL_ALLOW_EMPTY_PASSWORD: yes 18 | ports: 19 | - 3306:3306 20 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=10s --health-retries=10 21 | 22 | redis: 23 | image: redis 24 | ports: 25 | - 6379:6379 26 | options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=10s --health-retries=10 27 | 28 | steps: 29 | - name: Check out repository code 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup pnpm 33 | uses: pnpm/action-setup@v4 34 | 35 | - name: Install dependencies 36 | run: pnpm install --frozen-lockfile 37 | 38 | - name: Run tests 39 | run: pnpm run test 40 | env: 41 | DB_HOST: mariadb 42 | DB_PORT: ${{ job.services.mariadb.ports[3306] }} 43 | TEST_DB_HOST: mariadb 44 | TEST_DB_PORT: ${{ job.services.mariadb.ports[3306] }} 45 | TEST_DB_PASS: test_krist 46 | REDIS_HOST: redis 47 | REDIS_PORT: ${{ job.services.redis.ports[6379] }} 48 | PUBLIC_URL: localhost:8080 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node.js 2 | 3 | logs 4 | .grunt 5 | node_modules 6 | .npm 7 | 8 | npm-debug.log 9 | 10 | # JetBrains 11 | 12 | *.iml 13 | .idea/ 14 | *.ipr 15 | *.iws 16 | .idea_modules/ 17 | 18 | /config.js 19 | /errorreport.js 20 | 21 | /data.db 22 | /data/ 23 | 24 | /motd.txt 25 | 26 | /.package.json 27 | 28 | /static/docs 29 | /static/kristwallet 30 | 31 | .env 32 | .env* 33 | npm-debug.log* 34 | .eslintcache 35 | dist 36 | coverage 37 | mochawesome-report 38 | .nyc_output 39 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": "test/fixtures.ts", 3 | "reporter": "mochawesome", 4 | "exit": true, 5 | "node-option": ["import=tsx"] 6 | } 7 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@istanbuljs/nyc-config-typescript", 3 | "check-coverage": true, 4 | "all": true, 5 | "include": ["src/**/*.ts"], 6 | "exclude": ["test/**/*.*"], 7 | "reporter": [ 8 | "html", "lcov", "text", "text-summary" 9 | ], 10 | "report-dir": "coverage" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-node", 9 | "request": "launch", 10 | "name": "Launch Krist", 11 | "skipFiles": [ 12 | "/**" 13 | ], 14 | "program": "${workspaceFolder}/index.js" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "apidoc", 4 | "authed", 5 | "authlogs", 6 | "backticks", 7 | "basevalue", 8 | "cwebsocket", 9 | "dumpnames", 10 | "duvsr", 11 | "eqeqeq", 12 | "exphbs", 13 | "firstseen", 14 | "freenonce", 15 | "getbalance", 16 | "getbaseblockvalue", 17 | "getblockvalue", 18 | "getmoneysupply", 19 | "getnames", 20 | "getnewdomains", 21 | "getowner", 22 | "getwalletversion", 23 | "getwork", 24 | "ghaction", 25 | "ghcr", 26 | "gitlog", 27 | "Grafana", 28 | "idempotency", 29 | "isnull", 30 | "juvewcui", 31 | "keepalive", 32 | "keepalives", 33 | "kfartoolong", 34 | "knotfound", 35 | "Krist's", 36 | "kristwallet", 37 | "kwsgj", 38 | "lastblock", 39 | "Lemmy", 40 | "licence", 41 | "listnames", 42 | "listtx", 43 | "lmao", 44 | "lpush", 45 | "lrange", 46 | "ltrim", 47 | "mariadb", 48 | "metaname", 49 | "metanames", 50 | "mochawesome", 51 | "MOTD", 52 | "mysqladmin", 53 | "namebonus", 54 | "nlut", 55 | "octokit", 56 | "paren", 57 | "parens", 58 | "pushtx", 59 | "recenttx", 60 | "richapi", 61 | "sanitise", 62 | "sanitised", 63 | "setwork", 64 | "singleline", 65 | "submitblock", 66 | "subprotocol", 67 | "tonumber", 68 | "totalin", 69 | "totalout", 70 | "txes", 71 | "ulut", 72 | "unauthed", 73 | "unlut", 74 | "unsupplied", 75 | "walletversion", 76 | "websockets", 77 | "whatsnew" 78 | ], 79 | "psi-header.config": { 80 | "forceToTop": true, 81 | "blankLinesAfter": 1, 82 | "license": "GPL-3.0", 83 | "author": "Drew Edwards", 84 | "initials": "DE", 85 | "authorEmail": "krist@drew.contact", 86 | "company": "tmpim", 87 | "copyrightHolder": "Drew Edwards" 88 | }, 89 | "psi-header.changes-tracking": { 90 | "isActive": true, 91 | "include": ["javascript", "typescript"], 92 | "includeGlob": ["src/**/*.{js,jsx,ts,tsx}"], 93 | "excludeGlob": ["**/settings.json"], 94 | "autoHeader": "manualSave", 95 | "enforceHeader": true 96 | }, 97 | "psi-header.variables": [ 98 | ["projectCreationYear", "2016"] 99 | ], 100 | "psi-header.templates": [{ 101 | "language": "*", 102 | "template": [ 103 | "Copyright <> - <> <>, <>", 104 | "", 105 | "This file is part of Krist.", 106 | "", 107 | "Krist is free software: you can redistribute it and/or modify", 108 | "it under the terms of the GNU General Public License as published by", 109 | "the Free Software Foundation, either version 3 of the License, or", 110 | "(at your option) any later version.", 111 | "", 112 | "Krist is distributed in the hope that it will be useful,", 113 | "but WITHOUT ANY WARRANTY; without even the implied warranty of", 114 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the", 115 | "GNU General Public License for more details.", 116 | "", 117 | "You should have received a copy of the GNU General Public License", 118 | "along with Krist. If not, see .", 119 | "", 120 | "For more project information, see ." 121 | ] 122 | } 123 | ], 124 | "typescript.tsdk": "node_modules/typescript/lib" 125 | } 126 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build 2 | FROM node:20-alpine AS base 3 | 4 | RUN apk add git ca-certificates make g++ 5 | 6 | ENV PNPM_HOME="/pnpm" 7 | ENV PATH="$PNPM_HOME:$PATH" 8 | RUN corepack enable 9 | 10 | WORKDIR /app 11 | COPY . . 12 | 13 | FROM base AS prod-deps 14 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile 15 | 16 | FROM base AS build 17 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile 18 | RUN pnpm run build 19 | RUN pnpm run docs 20 | RUN cp /app/static/docs.bundle.js /app/static/docs/assets/main.bundle.js 21 | 22 | FROM base 23 | COPY --from=prod-deps /app/node_modules /app/node_modules 24 | COPY --from=build /app/dist /app/dist 25 | COPY --from=build /app/static/docs /app/static/docs 26 | EXPOSE 8080 27 | ENV NODE_ENV=production 28 | CMD ["node", "dist/src/index.js"] 29 | -------------------------------------------------------------------------------- /apidoc-template/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /apidoc-template/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /apidoc-template/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /apidoc-template/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /apidoc-template/img/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/img/favicon-128x128.png -------------------------------------------------------------------------------- /apidoc-template/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/img/favicon-16x16.png -------------------------------------------------------------------------------- /apidoc-template/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/img/favicon-32x32.png -------------------------------------------------------------------------------- /apidoc-template/img/favicon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/img/favicon-64x64.png -------------------------------------------------------------------------------- /apidoc-template/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/apidoc-template/img/favicon.ico -------------------------------------------------------------------------------- /apidoc-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs" 3 | } 4 | -------------------------------------------------------------------------------- /apidoc-template/src/diff_match_patch.mjs: -------------------------------------------------------------------------------- 1 | import _DiffMatchPatch from 'diff-match-patch'; 2 | 3 | export default class DiffMatchPatch extends _DiffMatchPatch { 4 | constructor (testMode) { 5 | super(); 6 | this.testMode = testMode; 7 | } 8 | 9 | diffMain (text1, text2, optChecklines, optDeadline) { 10 | return super.diff_main(this._stripHtml(text1), this._stripHtml(text2), optChecklines, optDeadline); 11 | } 12 | 13 | diffPrettyHtml (diffs) { 14 | const html = []; 15 | const patternAmp = /&/g; 16 | const patternLt = //g; 18 | const patternPara = /\n/g; 19 | for (let x = 0; x < diffs.length; x++) { 20 | const op = diffs[x][0]; // Operation (insert, delete, equal) 21 | const data = diffs[x][1]; // Text of change. 22 | const text = data.replace(patternAmp, '&').replace(patternLt, '<') 23 | .replace(patternGt, '>').replace(patternPara, '¶
'); 24 | switch (op) { 25 | case _DiffMatchPatch.DIFF_INSERT: 26 | html[x] = '' + text + ''; 27 | break; 28 | case _DiffMatchPatch.DIFF_DELETE: 29 | html[x] = '' + text + ''; 30 | break; 31 | case _DiffMatchPatch.DIFF_EQUAL: 32 | html[x] = '' + text + ''; 33 | break; 34 | } 35 | } 36 | return html.join(''); 37 | } 38 | 39 | diffCleanupSemantic (diffs) { 40 | return this.diff_cleanupSemantic(diffs); 41 | } 42 | 43 | _stripHtml (html) { 44 | // no document object with CLI when running tests 45 | if (this.testMode) { return html; } 46 | const div = document.createElement('div'); 47 | div.innerHTML = html; 48 | return div.textContent || div.innerText || ''; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apidoc-template/src/jsonifier.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * apidoc 3 | * https://apidocjs.com 4 | * 5 | * Authors: 6 | * Peter Rottmann 7 | * Nicolas CARPi @ Deltablot 8 | * Copyright (c) 2013 inveris OHG 9 | * Licensed under the MIT license. 10 | */ 11 | import pkg from 'lodash'; 12 | const { defaultsDeep } = pkg; 13 | 14 | const setValueToField = (fields, value) => { 15 | const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value }); 16 | return fields.reduceRight(reducer, {}); 17 | }; 18 | 19 | const fieldsToJson = fields => { 20 | let obj = {}; 21 | fields.forEach(field => { 22 | const line = setValueToField(field[0].split('.'), field[1]); 23 | obj = defaultsDeep(obj, line); 24 | }); 25 | return beautify(obj); 26 | }; 27 | 28 | /** 29 | * Stringify an obj to JSON with spaces 30 | */ 31 | export function beautify (obj) { 32 | return JSON.stringify(obj, null, 4); 33 | } 34 | 35 | export function body2json (context) { 36 | // build an array of fields with their type 37 | const fields = []; 38 | context.forEach(entry => { 39 | let val; 40 | switch (entry.type.toLowerCase()) { 41 | case 'string': 42 | val = entry.defaultValue || ''; 43 | break; 44 | case 'boolean': 45 | val = Boolean(entry.defaultValue) || false; 46 | break; 47 | case 'number': 48 | val = parseInt(entry.defaultValue || 0, 10); 49 | break; 50 | case 'date': 51 | // date field will have default value or formatted date of today in current locale 52 | val = entry.defaultValue || new Date().toLocaleDateString(window.navigator.language); 53 | break; 54 | } 55 | fields.push([entry.field, val]); 56 | }); 57 | return fieldsToJson(fields); 58 | } 59 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/ca.mjs: -------------------------------------------------------------------------------- 1 | export const ca = { 2 | 'Allowed values:': 'Valors permesos:', 3 | 'Compare all with predecessor': 'Comparar tot amb versió anterior', 4 | 'compare changes to:': 'comparar canvis amb:', 5 | 'compared to': 'comparat amb', 6 | 'Default value:': 'Valor per defecte:', 7 | Description: 'Descripció', 8 | Field: 'Camp', 9 | General: 'General', 10 | 'Generated with': 'Generat amb', 11 | Name: 'Nom', 12 | 'No response values.': 'Sense valors en la resposta.', 13 | optional: 'opcional', 14 | Parameter: 'Paràmetre', 15 | 'Permission:': 'Permisos:', 16 | Response: 'Resposta', 17 | Send: 'Enviar', 18 | 'Send a Sample Request': 'Enviar una petició d\'exemple', 19 | 'show up to version:': 'mostrar versió:', 20 | 'Size range:': 'Tamany de rang:', 21 | Type: 'Tipus', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/cs.mjs: -------------------------------------------------------------------------------- 1 | export const cs = { 2 | 'Allowed values:': 'Povolené hodnoty:', 3 | 'Compare all with predecessor': 'Porovnat vše s předchozími verzemi', 4 | 'compare changes to:': 'porovnat změny s:', 5 | 'compared to': 'porovnat s', 6 | 'Default value:': 'Výchozí hodnota:', 7 | Description: 'Popis', 8 | Field: 'Pole', 9 | General: 'Obecné', 10 | 'Generated with': 'Vygenerováno pomocí', 11 | Name: 'Název', 12 | 'No response values.': 'Nebyly vráceny žádné hodnoty.', 13 | optional: 'volitelné', 14 | Parameter: 'Parametr', 15 | 'Permission:': 'Oprávnění:', 16 | Response: 'Odpověď', 17 | Send: 'Odeslat', 18 | 'Send a Sample Request': 'Odeslat ukázkový požadavek', 19 | 'show up to version:': 'zobrazit po verzi:', 20 | 'Size range:': 'Rozsah velikosti:', 21 | Type: 'Typ', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/de.mjs: -------------------------------------------------------------------------------- 1 | export const de = { 2 | 'Allowed values:': 'Erlaubte Werte:', 3 | 'Compare all with predecessor': 'Vergleiche alle mit ihren Vorgängern', 4 | 'compare changes to:': 'vergleiche Änderungen mit:', 5 | 'compared to': 'verglichen mit', 6 | 'Default value:': 'Standardwert:', 7 | Description: 'Beschreibung', 8 | Field: 'Feld', 9 | General: 'Allgemein', 10 | 'Generated with': 'Erstellt mit', 11 | Name: 'Name', 12 | 'No response values.': 'Keine Rückgabewerte.', 13 | optional: 'optional', 14 | Parameter: 'Parameter', 15 | 'Permission:': 'Berechtigung:', 16 | Response: 'Antwort', 17 | Send: 'Senden', 18 | 'Send a Sample Request': 'Eine Beispielanfrage senden', 19 | 'show up to version:': 'zeige bis zur Version:', 20 | 'Size range:': 'Größenbereich:', 21 | Type: 'Typ', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/es.mjs: -------------------------------------------------------------------------------- 1 | export const es = { 2 | 'Allowed values:': 'Valores permitidos:', 3 | 'Compare all with predecessor': 'Comparar todo con versión anterior', 4 | 'compare changes to:': 'comparar cambios con:', 5 | 'compared to': 'comparado con', 6 | 'Default value:': 'Valor por defecto:', 7 | Description: 'Descripción', 8 | Field: 'Campo', 9 | General: 'General', 10 | 'Generated with': 'Generado con', 11 | Name: 'Nombre', 12 | 'No response values.': 'Sin valores en la respuesta.', 13 | optional: 'opcional', 14 | Parameter: 'Parámetro', 15 | 'Permission:': 'Permisos:', 16 | Response: 'Respuesta', 17 | Send: 'Enviar', 18 | 'Send a Sample Request': 'Enviar una petición de ejemplo', 19 | 'show up to version:': 'mostrar a versión:', 20 | 'Size range:': 'Tamaño de rango:', 21 | Type: 'Tipo', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/fr.mjs: -------------------------------------------------------------------------------- 1 | export const fr = { 2 | 'Allowed values:': 'Valeurs autorisées :', 3 | Body: 'Corps', 4 | 'Compare all with predecessor': 'Tout comparer avec ...', 5 | 'compare changes to:': 'comparer les changements à :', 6 | 'compared to': 'comparer à', 7 | 'Default value:': 'Valeur par défaut :', 8 | Description: 'Description', 9 | Field: 'Champ', 10 | General: 'Général', 11 | 'Generated with': 'Généré avec', 12 | Header: 'En-tête', 13 | Headers: 'En-têtes', 14 | Name: 'Nom', 15 | 'No response values.': 'Aucune valeur de réponse.', 16 | 'No value': 'Aucune valeur', 17 | optional: 'optionnel', 18 | Parameter: 'Paramètre', 19 | Parameters: 'Paramètres', 20 | 'Permission:': 'Permission :', 21 | 'Query Parameter(s)': 'Paramètre(s) de la requête', 22 | 'Query Parameters': 'Paramètres de la requête', 23 | 'Request Body': 'Corps de la requête', 24 | required: 'requis', 25 | Response: 'Réponse', 26 | Send: 'Envoyer', 27 | 'Send a Sample Request': 'Envoyer une requête représentative', 28 | 'show up to version:': 'Montrer à partir de la version :', 29 | 'Size range:': 'Ordre de grandeur :', 30 | Type: 'Type', 31 | url: 'url', 32 | }; 33 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/it.mjs: -------------------------------------------------------------------------------- 1 | export const it = { 2 | 'Allowed values:': 'Valori permessi:', 3 | 'Compare all with predecessor': 'Confronta tutto con versioni precedenti', 4 | 'compare changes to:': 'confronta modifiche con:', 5 | 'compared to': 'confrontato con', 6 | 'Default value:': 'Valore predefinito:', 7 | Description: 'Descrizione', 8 | Field: 'Campo', 9 | General: 'Generale', 10 | 'Generated with': 'Creato con', 11 | Name: 'Nome', 12 | 'No response values.': 'Nessun valore di risposta.', 13 | optional: 'opzionale', 14 | Parameter: 'Parametro', 15 | 'Permission:': 'Permessi:', 16 | Response: 'Risposta', 17 | Send: 'Invia', 18 | 'Send a Sample Request': 'Invia una richiesta di esempio', 19 | 'show up to version:': 'mostra alla versione:', 20 | 'Size range:': 'Intervallo dimensione:', 21 | Type: 'Tipo', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/locale.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * apidoc 3 | * https://apidocjs.com 4 | * 5 | * Authors: 6 | * Peter Rottmann 7 | * Nicolas CARPi @ Deltablot 8 | * Copyright (c) 2013 inveris OHG 9 | * Licensed under the MIT license. 10 | */ 11 | import { ca } from './ca.mjs'; 12 | import { cs } from './cs.mjs'; 13 | import { de } from './de.mjs'; 14 | import { es } from './es.mjs'; 15 | import { fr } from './fr.mjs'; 16 | import { it } from './it.mjs'; 17 | import { nl } from './nl.mjs'; 18 | import { pl } from './pl.mjs'; 19 | import { ptBr } from './pt_br.mjs'; 20 | import { ro } from './ro.mjs'; 21 | import { ru } from './ru.mjs'; 22 | import { tr } from './tr.mjs'; 23 | import { vi } from './vi.mjs'; 24 | import { zhCn } from './zh_cn.mjs'; 25 | 26 | const locales = { 27 | ca: ca, 28 | cs: cs, 29 | de: de, 30 | es: es, 31 | en: {}, 32 | fr: fr, 33 | it: it, 34 | nl: nl, 35 | pl: pl, 36 | pt: ptBr, 37 | ro: ro, 38 | ru: ru, 39 | tr: tr, 40 | vi: vi, 41 | zh: zhCn, 42 | }; 43 | 44 | // e.g. en fr pl 45 | export const lang = (window.navigator.language ?? 'en-GB').toLowerCase().substr(0, 2); 46 | 47 | export let locale = locales[lang] ? locales[lang] : locales.en; 48 | 49 | export function __ (text) { 50 | const index = locale[text]; 51 | if (index === undefined) { return text; } 52 | return index; 53 | } 54 | 55 | export function setLanguage (language) { 56 | locale = locales[language]; 57 | } 58 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/nl.mjs: -------------------------------------------------------------------------------- 1 | export const nl = { 2 | 'Allowed values:': 'Toegestane waarden:', 3 | 'Compare all with predecessor': 'Vergelijk alle met voorgaande versie', 4 | 'compare changes to:': 'vergelijk veranderingen met:', 5 | 'compared to': 'vergelijk met', 6 | 'Default value:': 'Standaard waarde:', 7 | Description: 'Omschrijving', 8 | Field: 'Veld', 9 | General: 'Algemeen', 10 | 'Generated with': 'Gegenereerd met', 11 | Name: 'Naam', 12 | 'No response values.': 'Geen response waardes.', 13 | optional: 'optioneel', 14 | Parameter: 'Parameter', 15 | 'Permission:': 'Permissie:', 16 | Response: 'Antwoorden', 17 | Send: 'Sturen', 18 | 'Send a Sample Request': 'Stuur een sample aanvragen', 19 | 'show up to version:': 'toon tot en met versie:', 20 | 'Size range:': 'Maatbereik:', 21 | Type: 'Type', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/pl.mjs: -------------------------------------------------------------------------------- 1 | export const pl = { 2 | 'Allowed values:': 'Dozwolone wartości:', 3 | 'Compare all with predecessor': 'Porównaj z poprzednimi wersjami', 4 | 'compare changes to:': 'porównaj zmiany do:', 5 | 'compared to': 'porównaj do:', 6 | 'Default value:': 'Wartość domyślna:', 7 | Description: 'Opis', 8 | Field: 'Pole', 9 | General: 'Generalnie', 10 | 'Generated with': 'Wygenerowano z', 11 | Name: 'Nazwa', 12 | 'No response values.': 'Brak odpowiedzi.', 13 | optional: 'opcjonalny', 14 | Parameter: 'Parametr', 15 | 'Permission:': 'Uprawnienia:', 16 | Response: 'Odpowiedź', 17 | Send: 'Wyślij', 18 | 'Send a Sample Request': 'Wyślij przykładowe żądanie', 19 | 'show up to version:': 'pokaż do wersji:', 20 | 'Size range:': 'Zakres rozmiaru:', 21 | Type: 'Typ', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/pt_br.mjs: -------------------------------------------------------------------------------- 1 | export const ptBr = { 2 | 'Allowed values:': 'Valores permitidos:', 3 | 'Compare all with predecessor': 'Compare todos com antecessores', 4 | 'compare changes to:': 'comparar alterações com:', 5 | 'compared to': 'comparado com', 6 | 'Default value:': 'Valor padrão:', 7 | Description: 'Descrição', 8 | Field: 'Campo', 9 | General: 'Geral', 10 | 'Generated with': 'Gerado com', 11 | Name: 'Nome', 12 | 'No response values.': 'Sem valores de resposta.', 13 | optional: 'opcional', 14 | Parameter: 'Parâmetro', 15 | 'Permission:': 'Permissão:', 16 | Response: 'Resposta', 17 | Send: 'Enviar', 18 | 'Send a Sample Request': 'Enviar um Exemplo de Pedido', 19 | 'show up to version:': 'aparecer para a versão:', 20 | 'Size range:': 'Faixa de tamanho:', 21 | Type: 'Tipo', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/ro.mjs: -------------------------------------------------------------------------------- 1 | export const ro = { 2 | 'Allowed values:': 'Valori permise:', 3 | 'Compare all with predecessor': 'Compară toate cu versiunea precedentă', 4 | 'compare changes to:': 'compară cu versiunea:', 5 | 'compared to': 'comparat cu', 6 | 'Default value:': 'Valoare implicită:', 7 | Description: 'Descriere', 8 | Field: 'Câmp', 9 | General: 'General', 10 | 'Generated with': 'Generat cu', 11 | Name: 'Nume', 12 | 'No response values.': 'Nici o valoare returnată.', 13 | optional: 'opțional', 14 | Parameter: 'Parametru', 15 | 'Permission:': 'Permisiune:', 16 | Response: 'Răspuns', 17 | Send: 'Trimite', 18 | 'Send a Sample Request': 'Trimite o cerere de probă', 19 | 'show up to version:': 'arată până la versiunea:', 20 | 'Size range:': 'Interval permis:', 21 | Type: 'Tip', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/ru.mjs: -------------------------------------------------------------------------------- 1 | export const ru = { 2 | 'Allowed values:': 'Допустимые значения:', 3 | 'Compare all with predecessor': 'Сравнить с предыдущей версией', 4 | 'compare changes to:': 'сравнить с:', 5 | 'compared to': 'в сравнении с', 6 | 'Default value:': 'По умолчанию:', 7 | Description: 'Описание', 8 | Field: 'Название', 9 | General: 'Общая информация', 10 | 'Generated with': 'Сгенерировано с помощью', 11 | Name: 'Название', 12 | 'No response values.': 'Нет значений для ответа.', 13 | optional: 'необязательный', 14 | Parameter: 'Параметр', 15 | 'Permission:': 'Разрешено:', 16 | Response: 'Ответ', 17 | Send: 'Отправить', 18 | 'Send a Sample Request': 'Отправить тестовый запрос', 19 | 'show up to version:': 'показать версию:', 20 | 'Size range:': 'Ограничения:', 21 | Type: 'Тип', 22 | url: 'URL', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/tr.mjs: -------------------------------------------------------------------------------- 1 | export const tr = { 2 | 'Allowed values:': 'İzin verilen değerler:', 3 | 'Compare all with predecessor': 'Tümünü öncekiler ile karşılaştır', 4 | 'compare changes to:': 'değişiklikleri karşılaştır:', 5 | 'compared to': 'karşılaştır', 6 | 'Default value:': 'Varsayılan değer:', 7 | Description: 'Açıklama', 8 | Field: 'Alan', 9 | General: 'Genel', 10 | 'Generated with': 'Oluşturan', 11 | Name: 'İsim', 12 | 'No response values.': 'Dönüş verisi yok.', 13 | optional: 'opsiyonel', 14 | Parameter: 'Parametre', 15 | 'Permission:': 'İzin:', 16 | Response: 'Dönüş', 17 | Send: 'Gönder', 18 | 'Send a Sample Request': 'Örnek istek gönder', 19 | 'show up to version:': 'bu versiyona kadar göster:', 20 | 'Size range:': 'Boyut aralığı:', 21 | Type: 'Tip', 22 | url: 'url', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/vi.mjs: -------------------------------------------------------------------------------- 1 | export const vi = { 2 | 'Allowed values:': 'Giá trị chấp nhận:', 3 | 'Compare all with predecessor': 'So sánh với tất cả phiên bản trước', 4 | 'compare changes to:': 'so sánh sự thay đổi với:', 5 | 'compared to': 'so sánh với', 6 | 'Default value:': 'Giá trị mặc định:', 7 | Description: 'Chú thích', 8 | Field: 'Trường dữ liệu', 9 | General: 'Tổng quan', 10 | 'Generated with': 'Được tạo bởi', 11 | Name: 'Tên', 12 | 'No response values.': 'Không có kết quả trả về.', 13 | optional: 'Tùy chọn', 14 | Parameter: 'Tham số', 15 | 'Permission:': 'Quyền hạn:', 16 | Response: 'Kết quả', 17 | Send: 'Gửi', 18 | 'Send a Sample Request': 'Gửi một yêu cầu mẫu', 19 | 'show up to version:': 'hiển thị phiên bản:', 20 | 'Size range:': 'Kích cỡ:', 21 | Type: 'Kiểu', 22 | url: 'liên kết', 23 | }; 24 | -------------------------------------------------------------------------------- /apidoc-template/src/locales/zh_cn.mjs: -------------------------------------------------------------------------------- 1 | export const zhCn = { 2 | 'Allowed values:': '允许值:', 3 | Body: '请求体', 4 | 'Compare all with predecessor': '与所有之前的版本比较', 5 | 'compare changes to:': '将当前版本与指定版本比较:', 6 | 'compared to': '相比于', 7 | 'Default value:': '默认值:', 8 | Description: '描述', 9 | Field: '字段', 10 | General: '概要', 11 | 'Generated with': '构建于', 12 | Name: '名称', 13 | 'No response values.': '无返回值.', 14 | optional: '可选', 15 | Parameter: '参数', 16 | Parameters: '参数', 17 | Headers: '请求头', 18 | 'Permission:': '权限:', 19 | Response: '返回', 20 | required: '必需的', 21 | Send: '发送', 22 | 'Send a Sample Request': '发送示例请求', 23 | 'show up to version:': '显示指定版本:', 24 | 'Size range:': '取值范围:', 25 | Type: '类型', 26 | url: '地址', 27 | }; 28 | -------------------------------------------------------------------------------- /apidoc-template/src/sampreq_url_processor.mjs: -------------------------------------------------------------------------------- 1 | /* 2 | * apidoc 3 | * https://apidocjs.com 4 | * 5 | * Authors: 6 | * Peter Rottmann 7 | * Nicolas CARPi @ Deltablot 8 | * Copyright (c) 2013 inveris OHG 9 | * Licensed under the MIT license. 10 | */ 11 | export default class UrlProcessor { 12 | // Replace parameters from url (:id) by the parameters from input values 13 | hydrate (url, queryParameters) { 14 | // The dummy URL base is only used for parses of relative URLs in Node.js. 15 | const parsedUrl = new URL(url, typeof window === 'undefined' ? 'https://dummy.base' : window.location.origin); 16 | const queryParametersChangedInPathname = {}; 17 | 18 | // For API parameters in the URL parts delimited by `/` (e.g. `/:foo/:bar`). 19 | parsedUrl.pathname.split('/').forEach((pathnamePart, i) => { 20 | if (pathnamePart.charAt(0) === ':') { 21 | const realPathnamePart = pathnamePart.slice(1); 22 | 23 | if (typeof queryParameters[realPathnamePart] !== 'undefined') { 24 | parsedUrl.pathname = parsedUrl.pathname.replace(pathnamePart, encodeURIComponent(queryParameters[realPathnamePart])); 25 | queryParametersChangedInPathname[realPathnamePart] = queryParameters[realPathnamePart]; 26 | } 27 | } 28 | }); 29 | 30 | // For API parameters in the URL query string (e.g. `?foo=:foo&bar=:bar`). 31 | for (const key in queryParameters) { 32 | if ( 33 | typeof queryParametersChangedInPathname[key] === 'undefined' || // Avoid adding query parameter if it has already been changed in pathname. 34 | parsedUrl.searchParams.has(key) 35 | ) { 36 | parsedUrl.searchParams.set(key, queryParameters[key]); 37 | } 38 | } 39 | 40 | return parsedUrl.toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /apidoc-template/src/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * apidoc 3 | * https://apidocjs.com 4 | * 5 | * Authors: 6 | * Peter Rottmann 7 | * Nicolas CARPi @ Deltablot 8 | * Copyright (c) 2013 inveris OHG 9 | * Licensed under the MIT license. 10 | */ 11 | 12 | /* webpack js bundler config file */ 13 | const path = require('path'); 14 | const { ESBuildMinifyPlugin } = require('esbuild-loader'); 15 | 16 | module.exports = { 17 | entry: path.resolve(__dirname, 'main.js'), 18 | // mode is set at runtime 19 | resolve: { 20 | alias: { 21 | handlebars: 'handlebars/dist/handlebars.min.js', 22 | // use src jquery, not the minified version or it won't be found 23 | jquery: 'jquery/src/jquery', 24 | }, 25 | extensions: ['.js', '.mjs'], 26 | }, 27 | module: { 28 | rules: [ 29 | // expose jquery globally 30 | { 31 | test: require.resolve('jquery'), 32 | loader: 'expose-loader', 33 | options: { 34 | exposes: ['$', 'jQuery'], 35 | }, 36 | }, 37 | ], 38 | }, 39 | output: { 40 | filename: 'main.bundle.js', 41 | // path is set at runtime 42 | }, 43 | optimization: { 44 | minimizer: [ 45 | new ESBuildMinifyPlugin({ 46 | target: 'es2015', 47 | }), 48 | ], 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /casket_example.casket: -------------------------------------------------------------------------------- 1 | krist.dev { 2 | root /var/www/krist.dev/static 3 | 4 | log /var/log/casket/krist.dev.access.log 5 | errors /var/log/casket/krist.dev.error.log { 6 | 502 down.html 7 | } 8 | 9 | proxy / 127.0.0.1:8080 { 10 | transparent 11 | except /style.css 12 | } 13 | 14 | cors 15 | gzip 16 | } 17 | 18 | ws.krist.dev { 19 | root /var/www/krist.dev/static 20 | 21 | log /var/log/casket/ws.krist.dev.access.log 22 | errors /var/log/casket/ws.krist.dev.error.log { 23 | 502 down.html 24 | } 25 | 26 | proxy /ws/gateway 127.0.0.1:8080 { 27 | transparent 28 | websocket 29 | } 30 | 31 | cors 32 | gzip 33 | } 34 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from "@eslint/js"; 4 | import tseslint from "typescript-eslint"; 5 | import mochaPlugin from "eslint-plugin-mocha"; 6 | import codegenPlugin from "eslint-plugin-codegen"; 7 | 8 | export default tseslint.config( 9 | { 10 | ignores: [ 11 | "node_modules", 12 | "static", 13 | "views", 14 | "data", 15 | "dist", 16 | "coverage", 17 | "mochawesome-report", 18 | ".nyc_output", 19 | ], 20 | }, 21 | eslint.configs.recommended, 22 | ...tseslint.configs.recommended, 23 | mochaPlugin.configs.flat.recommended, 24 | { 25 | plugins: { 26 | "@typescript-eslint": tseslint.plugin, 27 | "codegen": codegenPlugin, 28 | }, 29 | languageOptions: { 30 | parser: tseslint.parser, 31 | parserOptions: { 32 | project: true 33 | }, 34 | } 35 | }, 36 | { 37 | files: [ 38 | "src/**/*.ts", 39 | "test/**/*.ts", 40 | ], 41 | rules: { 42 | quotes: ["error", "double", { allowTemplateLiterals: true }], 43 | semi: "error", 44 | indent: ["error", 2, { 45 | FunctionDeclaration: { parameters: "first" } 46 | }], 47 | "prefer-arrow-callback": "warn", 48 | "eol-last": ["error", "always"], 49 | "object-shorthand": ["error", "always"], 50 | "no-unused-vars": 0, 51 | "no-lonely-if": "warn", 52 | "no-trailing-spaces": "warn", 53 | "no-whitespace-before-property": "warn", 54 | "no-multiple-empty-lines": "warn", 55 | "space-before-blocks": "warn", 56 | "space-in-parens": ["warn", "never"], 57 | "space-infix-ops": "warn", 58 | "eqeqeq": "warn", 59 | "no-shadow": ["error"], 60 | "no-var": ["error"], 61 | "prefer-const": ["error"], 62 | "one-var": ["error", { separateRequires: true }], 63 | 64 | "@typescript-eslint/no-explicit-any": 0, 65 | "@typescript-eslint/explicit-module-boundary-types": ["warn", { 66 | allowArgumentsExplicitlyTypedAsAny: true, 67 | allowDirectConstAssertionInArrowFunctions: true, 68 | allowedNames: [], 69 | allowHigherOrderFunctions: true, 70 | allowTypedFunctionExpressions: true 71 | }], 72 | "@typescript-eslint/consistent-type-definitions": ["error", "interface"], 73 | "@typescript-eslint/member-delimiter-style": ["error", { 74 | multiline: { delimiter: "semi", requireLast: true }, 75 | singleline: { delimiter: "semi", requireLast: false } 76 | }], 77 | "@typescript-eslint/no-unused-vars": ["warn", { 78 | ignoreRestSiblings: true, 79 | argsIgnorePattern: "^_" 80 | }], 81 | "@typescript-eslint/no-non-null-assertion": 0, 82 | "@typescript-eslint/space-before-function-paren": ["warn", { 83 | anonymous: "never", 84 | named: "never", 85 | asyncArrow: "always" 86 | }], 87 | 88 | "@typescript-eslint/naming-convention": [ 89 | "error", 90 | { selector: "typeLike", format: ["StrictPascalCase"] }, 91 | { selector: "variable", format: ["strictCamelCase", "UPPER_CASE", "StrictPascalCase"] }, 92 | { selector: "variable", modifiers: ["destructured"], format: null } 93 | ], 94 | 95 | "codegen/codegen": "error" 96 | } 97 | }, 98 | { 99 | files: [ 100 | "test/**/*.ts" 101 | ], 102 | rules: { 103 | "prefer-arrow-callback": "off", 104 | "mocha/no-identical-title": "off", 105 | "mocha/no-setup-in-describe": "off", 106 | "mocha/max-top-level-suites": "off", 107 | } 108 | } 109 | ); 110 | -------------------------------------------------------------------------------- /src/controllers/addresses.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Address, Limit, Offset, PaginatedResult } from "../database/index.js"; 23 | import { ErrorAddressNotFound, ErrorInvalidParameter, ErrorMissingParameter } from "../errors/index.js"; 24 | import { getAddress, getAddresses, getRichAddresses } from "../krist/addresses/index.js"; 25 | import { isValidKristAddress, makeV2Address, validateLimitOffset } from "../utils/index.js"; 26 | 27 | export async function ctrlGetAddresses( 28 | limit: Limit, 29 | offset: Offset 30 | ): Promise> { 31 | await validateLimitOffset(limit, offset); 32 | return getAddresses(limit, offset); 33 | } 34 | 35 | export async function ctrlGetRichAddresses( 36 | limit: Limit, 37 | offset: Offset 38 | ): Promise> { 39 | await validateLimitOffset(limit, offset); 40 | return getRichAddresses(limit, offset); 41 | } 42 | 43 | export async function ctrlGetAddress( 44 | address?: string, 45 | fetchNames?: boolean 46 | ): Promise
{ 47 | if (!address) throw new ErrorMissingParameter("address"); 48 | if (!isValidKristAddress(address)) throw new ErrorInvalidParameter("address"); 49 | 50 | const result = await getAddress(address, !!fetchNames); 51 | if (!result) throw new ErrorAddressNotFound(address); 52 | 53 | return result; 54 | } 55 | 56 | export async function ctrlGetAddressAlert( 57 | privatekey?: string 58 | ): Promise { 59 | if (!privatekey) throw new ErrorMissingParameter("privatekey"); 60 | const address = makeV2Address(privatekey); 61 | const result = await getAddress(address); 62 | if (!result) throw new ErrorAddressNotFound(address); 63 | return result.alert ?? null; 64 | } 65 | -------------------------------------------------------------------------------- /src/controllers/blocks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Request } from "express"; 23 | import { isNaN } from "lodash-es"; 24 | import { Block, Limit, Offset, PaginatedResult } from "../database/index.js"; 25 | import { 26 | ErrorBlockNotFound, 27 | ErrorInvalidParameter, 28 | ErrorMiningDisabled, 29 | ErrorMissingParameter 30 | } from "../errors/index.js"; 31 | import { getBlock, getBlocks, getLastBlock, getLowestHashes } from "../krist/blocks/index.js"; 32 | import { submitBlock, SubmitBlockResponse } from "../krist/blocks/submit.js"; 33 | import { isMiningEnabled } from "../krist/switches.js"; 34 | import { isValidKristAddress, validateLimitOffset } from "../utils/index.js"; 35 | import { NONCE_MAX_SIZE } from "../utils/vars.js"; 36 | 37 | export async function ctrlGetBlocks( 38 | limit: Limit, 39 | offset: Offset, 40 | asc?: boolean 41 | ): Promise> { 42 | await validateLimitOffset(limit, offset); 43 | return getBlocks(limit, offset, asc); 44 | } 45 | 46 | export async function ctrlGetLastBlock(): Promise { 47 | const block = await getLastBlock(); 48 | if (!block) throw new ErrorBlockNotFound(); 49 | return block; 50 | } 51 | 52 | export async function ctrlGetLowestHashes( 53 | limit: Limit, 54 | offset: Offset 55 | ): Promise> { 56 | await validateLimitOffset(limit, offset); 57 | return getLowestHashes(limit, offset); 58 | } 59 | 60 | export async function ctrlGetBlock( 61 | height?: string | number 62 | ): Promise { 63 | if (height === undefined) throw new ErrorMissingParameter("height"); 64 | if (isNaN(height)) throw new ErrorInvalidParameter("height"); 65 | 66 | const blockId = Math.max( 67 | typeof height === "string" ? parseInt(height) : height, 68 | 0 69 | ); 70 | 71 | const block = await getBlock(blockId); 72 | if (!block) throw new ErrorBlockNotFound(); 73 | return block; 74 | } 75 | 76 | export async function ctrlSubmitBlock( 77 | req: Request, 78 | address?: string, 79 | rawNonce?: number[] | string 80 | ): Promise { 81 | if (!await isMiningEnabled()) throw new ErrorMiningDisabled(); 82 | 83 | if (!address) throw new ErrorMissingParameter("address"); 84 | if (!isValidKristAddress(address, true)) 85 | throw new ErrorInvalidParameter("address"); 86 | 87 | if (!rawNonce) throw new ErrorMissingParameter("nonce"); 88 | if (rawNonce.length < 1 || rawNonce.length > NONCE_MAX_SIZE) 89 | throw new ErrorInvalidParameter("nonce"); 90 | 91 | return submitBlock(req, address, rawNonce); 92 | } 93 | -------------------------------------------------------------------------------- /src/database/migrations/2024-06-27T17-59-00-cleanup.ts: -------------------------------------------------------------------------------- 1 | import type { MigrationFn } from "umzug"; 2 | import { DataTypes, Sequelize, sql } from "@sequelize/core"; 3 | 4 | const up: MigrationFn = async ({ context: sq }) => { 5 | // Fix some issues with the original schema 6 | await sq.queryInterface.changeColumn("addresses", "address", { 7 | type: DataTypes.CHAR(10), // change from VARCHAR to CHAR 8 | allowNull: false 9 | }); 10 | await sq.queryInterface.changeColumn("addresses", "balance", { 11 | type: DataTypes.INTEGER.UNSIGNED, 12 | allowNull: false 13 | }); 14 | await sq.queryInterface.changeColumn("addresses", "totalin", { 15 | type: DataTypes.INTEGER.UNSIGNED, 16 | allowNull: false 17 | }); 18 | await sq.queryInterface.changeColumn("addresses", "totalout", { 19 | type: DataTypes.INTEGER.UNSIGNED, 20 | allowNull: false 21 | }); 22 | await sq.queryInterface.changeColumn("addresses", "firstseen", { 23 | type: DataTypes.DATE, 24 | allowNull: false 25 | }); 26 | 27 | // There are 8 blocks in production with addresses that are shorter than 10 characters. Right-pad them with zeroes 28 | // to be the correct length. 29 | await sq.queryInterface.bulkUpdate("blocks", { 30 | address: sql`LPAD(address, 10, '0')` 31 | }, sql`LENGTH(address) < 10`); 32 | await sq.queryInterface.changeColumn("blocks", "address", { 33 | type: DataTypes.CHAR(10), // change from VARCHAR to CHAR. 34 | allowNull: false 35 | }); 36 | 37 | await sq.queryInterface.changeColumn("names", "name", { 38 | type: DataTypes.STRING(64), 39 | allowNull: false 40 | }); 41 | await sq.queryInterface.changeColumn("names", "owner", { 42 | type: DataTypes.CHAR(10), 43 | allowNull: false 44 | }); 45 | await sq.queryInterface.changeColumn("names", "original_owner", { 46 | type: DataTypes.CHAR(10), 47 | allowNull: false 48 | }); 49 | await sq.queryInterface.changeColumn("names", "registered", { 50 | type: DataTypes.DATE, 51 | allowNull: false 52 | }); 53 | await sq.queryInterface.changeColumn("names", "unpaid", { 54 | type: DataTypes.STRING(255), 55 | allowNull: false 56 | }); 57 | 58 | await sq.queryInterface.changeColumn("transactions", "value", { 59 | type: DataTypes.INTEGER.UNSIGNED, 60 | allowNull: false 61 | }); 62 | await sq.queryInterface.changeColumn("transactions", "time", { 63 | type: DataTypes.DATE, 64 | allowNull: false 65 | }); 66 | 67 | await sq.queryInterface.changeColumn("authlogs", "address", { 68 | type: DataTypes.CHAR(10), 69 | allowNull: false 70 | }); 71 | await sq.queryInterface.changeColumn("authlogs", "time", { 72 | type: DataTypes.DATE, 73 | allowNull: false 74 | }); 75 | }; 76 | 77 | const down: MigrationFn = async ({ context: sq }) => { 78 | await sq.queryInterface.changeColumn("addresses", "address", { 79 | type: DataTypes.STRING(10), 80 | allowNull: true 81 | }); 82 | await sq.queryInterface.changeColumn("blocks", "address", { 83 | type: DataTypes.STRING(10), 84 | allowNull: false 85 | }); 86 | }; 87 | 88 | export default { up, down }; 89 | -------------------------------------------------------------------------------- /src/database/migrations/2024-06-27T19-07-00-add-request-id.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes, Sequelize } from "@sequelize/core"; 2 | import type { MigrationFn } from "umzug"; 3 | 4 | const up: MigrationFn = async ({ context: sq }) => { 5 | // Add request_id to transactions 6 | await sq.queryInterface.addColumn("transactions", "request_id", { 7 | type: DataTypes.UUID, 8 | allowNull: true 9 | }); 10 | await sq.queryInterface.addIndex("transactions", ["request_id"], { unique: true }); 11 | }; 12 | 13 | const down: MigrationFn = async ({ context: sq }) => { 14 | await sq.queryInterface.removeColumn("transactions", "request_id"); 15 | }; 16 | 17 | export default { up, down }; 18 | -------------------------------------------------------------------------------- /src/database/migrations/barrel.ts: -------------------------------------------------------------------------------- 1 | // The content in this file is autogenerated by eslint - run `pnpm lint:codegen` to update it. 2 | // See https://npmjs.com/package/eslint-plugin-codegen for more details. 3 | 4 | // codegen:start {preset: barrel, include: './*.ts', import: default, extension: {ts: 'js'}, export: {name: migrations, keys: path}} 5 | import _20240627T175800Init from "./2024-06-27T17-58-00-init.js"; 6 | import _20240627T175900Cleanup from "./2024-06-27T17-59-00-cleanup.js"; 7 | import _20240627T190700AddRequestId from "./2024-06-27T19-07-00-add-request-id.js"; 8 | 9 | export const migrations = { 10 | "./2024-06-27T17-58-00-init.js": _20240627T175800Init, 11 | "./2024-06-27T17-59-00-cleanup.js": _20240627T175900Cleanup, 12 | "./2024-06-27T19-07-00-add-request-id.js": _20240627T190700AddRequestId 13 | }; 14 | // codegen:end 15 | -------------------------------------------------------------------------------- /src/database/redis.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { createClient } from "redis"; 24 | 25 | import { REDIS_HOST, REDIS_PASS, REDIS_PORT, REDIS_PREFIX } from "../utils/vars.js"; 26 | 27 | export const redis = createClient({ 28 | socket: { 29 | host: REDIS_HOST, 30 | port: REDIS_PORT 31 | }, 32 | password: REDIS_PASS 33 | }); 34 | 35 | redis.on("ready", () => 36 | console.log(chalkT`{green [Redis]} Connected`)); 37 | redis.on("error", (err: Error) => 38 | console.error(chalkT`{red [Redis]} Error:`, err)); 39 | redis.on("end", () => 40 | console.error(chalkT`{red [Redis]} Disconnected`)); 41 | redis.on("reconnecting", () => 42 | console.error(chalkT`{cyan [Redis]} Reconnecting`)); 43 | 44 | export async function initRedis(): Promise { 45 | console.log(chalkT`{cyan [Redis]} Connecting to redis`); 46 | await redis.connect(); 47 | await redis.ping(); 48 | } 49 | 50 | export async function shutdownRedis(): Promise { 51 | console.log(chalkT`{cyan [Redis]} Disconnecting from redis`); 52 | await redis.disconnect(); 53 | console.log(chalkT`{green [Redis]} Disconnected`); 54 | } 55 | 56 | export const rKey = (key: string): string => `${REDIS_PREFIX}${key}`; 57 | -------------------------------------------------------------------------------- /src/docs.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | /** 23 | * @apiDefine URLParameter URL Parameter 24 | * 25 | * A RESTful URL parameter. 26 | */ 27 | 28 | /** 29 | * @apiDefine QueryParameter Query String Parameter 30 | * 31 | * A GET parameter in the query string. 32 | */ 33 | 34 | /** 35 | * @apiDefine BodyParameter Body Parameter 36 | * 37 | * A POST parameter in the request body. 38 | */ 39 | 40 | /** 41 | * @apiDefine WebsocketParameter Websocket Message Parameter 42 | * 43 | * A parameter encoded in JSON in the websocket message. 44 | */ 45 | 46 | /** 47 | * @apiDefine MiscellaneousGroup Miscellaneous 48 | * 49 | * All other endpoints. 50 | */ 51 | 52 | export {}; 53 | -------------------------------------------------------------------------------- /src/errors/KristError.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export class KristError extends Error { 23 | constructor( 24 | message: string, 25 | public errorString: string = message, 26 | public statusCode: number = 400, 27 | public info?: InfoT 28 | ) { 29 | super(message); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/errors/addresses.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorAddressNotFound extends KristError<{ address?: string | null }> { 25 | constructor(address: string) { 26 | super( 27 | `Address ${address ?? "[null]"} not found`, 28 | "address_not_found", 29 | 404, 30 | { address } 31 | ); 32 | } 33 | } 34 | 35 | export class ErrorAuthFailed extends KristError { 36 | constructor() { 37 | super("Authentication failed", "auth_failed", 401); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/errors/blocks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorBlockNotFound extends KristError { 25 | constructor() { 26 | super("Block not found", "block_not_found", 404); 27 | } 28 | } 29 | 30 | export class ErrorSolutionIncorrect extends KristError { 31 | constructor() { 32 | super("Solution incorrect", "solution_incorrect", 403); 33 | } 34 | } 35 | 36 | export class ErrorSolutionDuplicate extends KristError { 37 | constructor() { 38 | super("Solution duplicate", "solution_duplicate", 409); 39 | } 40 | } 41 | 42 | export class ErrorMiningDisabled extends KristError { 43 | constructor() { 44 | super("Mining disabled", "mining_disabled", 423); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/errors/generic.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorInvalidParameter extends KristError<{ 25 | parameter: string; 26 | message?: string; 27 | }> { 28 | constructor( 29 | public parameter: string, 30 | public message = `Invalid parameter ${parameter}`, 31 | public errorString = "invalid_parameter" 32 | ) { 33 | super(message, errorString, 400, { parameter }); 34 | } 35 | } 36 | 37 | export class ErrorMissingParameter extends ErrorInvalidParameter { 38 | constructor( 39 | public parameter: string, 40 | public message = `Missing parameter ${parameter}`, 41 | public errorString = "missing_parameter" 42 | ) { 43 | super(parameter, message, errorString); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/errors/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export * from "./addresses.js"; 23 | export * from "./blocks.js"; 24 | export * from "./generic.js"; 25 | export * from "./KristError.js"; 26 | export * from "./names.js"; 27 | export * from "./sendErrors.js"; 28 | export * from "./transactions.js"; 29 | export * from "./webserver.js"; 30 | export * from "./websockets.js"; 31 | -------------------------------------------------------------------------------- /src/errors/names.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorNameNotFound extends KristError<{ name?: string | null }> { 25 | constructor(name: string) { 26 | super( 27 | `Name ${name ?? "[null]"} not found`, 28 | "name_not_found", 29 | 404, 30 | { name } 31 | ); 32 | } 33 | } 34 | 35 | export class ErrorNameTaken extends KristError<{ name?: string | null }> { 36 | constructor(name: string) { 37 | super( 38 | `Name ${name ?? "[null]"} is already taken`, 39 | "name_taken", 40 | 409, 41 | { name } 42 | ); 43 | } 44 | } 45 | 46 | export class ErrorNotNameOwner extends KristError<{ name?: string | null }> { 47 | constructor(name: string) { 48 | super( 49 | `You are not the owner of name ${name ?? "[null]"}`, 50 | "not_name_owner", 51 | 403, 52 | { name } 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/errors/sendErrors.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | 24 | import { KristError } from "./KristError.js"; 25 | 26 | export interface ErrorResponse { 27 | ok: false; 28 | error: string; 29 | message?: string; 30 | } 31 | 32 | export function errorToJson(err: unknown): ErrorResponse { 33 | if (err instanceof KristError) { 34 | return { 35 | ok: false, 36 | error: err.errorString, 37 | message: err.message, 38 | ...(err.info || {}) 39 | }; 40 | } else if (err instanceof Error 41 | && err.name === "SequelizeUniqueConstraintError") { 42 | console.error(chalkT`{red [Error]} Uncaught validation error.`); 43 | console.error(err.stack); 44 | console.error((err as any).errors); 45 | 46 | return { 47 | ok: false, 48 | error: "server_error" 49 | }; 50 | } else { 51 | console.error(chalkT`{red [Error]} Uncaught error:`, err); 52 | if (err instanceof Error) console.error(err.stack); 53 | 54 | return { 55 | ok: false, 56 | error: "server_error" 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/errors/transactions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorInsufficientFunds extends KristError { 25 | constructor() { 26 | super("Insufficient funds", "insufficient_funds", 403); 27 | } 28 | } 29 | 30 | export class ErrorTransactionNotFound extends KristError { 31 | constructor() { 32 | super("Transaction not found", "transaction_not_found", 404); 33 | } 34 | } 35 | 36 | export class ErrorTransactionsDisabled extends KristError { 37 | constructor() { 38 | super("Transactions disabled", "transactions_disabled", 423); 39 | } 40 | } 41 | 42 | export class ErrorTransactionConflict extends KristError<{ 43 | parameter: string; 44 | }> { 45 | constructor( 46 | public parameter: string, 47 | public message = `Transaction conflict for parameter ${parameter}`, 48 | public errorString = "transaction_conflict" 49 | ) { 50 | super(message, errorString, 409, { parameter }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/errors/webserver.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorRouteNotFound extends KristError { 25 | constructor() { 26 | super("Route not found", "route_not_found", 404); 27 | } 28 | } 29 | 30 | export class ErrorRateLimitHit extends KristError { 31 | constructor() { 32 | super("Rate limit hit", "rate_limit_hit", 429); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/errors/websockets.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { KristError } from "./KristError.js"; 23 | 24 | export class ErrorInvalidWebsocketToken extends KristError { 25 | constructor() { 26 | super("Invalid websocket token", "invalid_websocket_token", 403); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import "dotenv/config"; 23 | 24 | import chalkT from "chalk-template"; 25 | import packageJson from "../package.json" with { type: "json" }; 26 | import { initDatabase, shutdownDb } from "./database/index.js"; 27 | import { initRedis, shutdownRedis } from "./database/redis.js"; 28 | import { initAuthLogCleanup, shutdownAuthLogCleanup } from "./krist/authLog.js"; 29 | import { initKrist } from "./krist/index.js"; 30 | import { initWorkOverTime, shutdownWorkOverTime } from "./krist/work.js"; 31 | import { initCriticalLogUpdater } from "./utils/criticalLog.js"; 32 | 33 | import { checkEnvVars } from "./utils/index.js"; 34 | import { initWebserver, shutdownWebserver } from "./webserver/index.js"; 35 | import { initWebSocketIpc, shutdownWebSocketIpc } from "./websockets/ipc.js"; 36 | 37 | async function main() { 38 | console.log(chalkT`Starting {bold ${packageJson.name}} {blue ${packageJson.version}}...`); 39 | 40 | checkEnvVars(); 41 | await initRedis(); 42 | await initDatabase(); 43 | await initKrist(); 44 | initWorkOverTime(); 45 | initAuthLogCleanup(); 46 | initCriticalLogUpdater(); 47 | await initWebSocketIpc(); 48 | await initWebserver(); 49 | 50 | console.log(chalkT`{bold ${packageJson.name}} {blue ${packageJson.version}} is ready!`); 51 | } 52 | 53 | function shutdown() { 54 | (async () => { 55 | shutdownWebserver(); 56 | await shutdownRedis(); 57 | await shutdownDb(); 58 | shutdownWorkOverTime(); 59 | shutdownAuthLogCleanup(); 60 | shutdownWebSocketIpc(); 61 | })().catch(console.error); 62 | } 63 | 64 | process.on("SIGINT", shutdown); 65 | process.on("SIGTERM", shutdown); 66 | 67 | main().catch(console.error); 68 | -------------------------------------------------------------------------------- /src/krist/addresses/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { QueryTypes } from "@sequelize/core"; 23 | import { Address, db, Limit, Offset, PaginatedResult } from "../../database/index.js"; 24 | import { sanitiseLimit, sanitiseOffset } from "../../utils/index.js"; 25 | 26 | export interface AddressWithNames extends Address { 27 | names?: number; 28 | } 29 | 30 | export async function getAddress( 31 | address: string, 32 | fetchNames?: boolean 33 | ): Promise { 34 | if (fetchNames) { 35 | // Fetch the name count if requested 36 | const rows = await db.query
(` 37 | SELECT 38 | \`addresses\`.*, 39 | COUNT(\`names\`.\`id\`) AS \`names\` 40 | FROM \`addresses\` 41 | LEFT JOIN \`names\` ON \`addresses\`.\`address\` = \`names\`.\`owner\` 42 | WHERE \`addresses\`.\`address\` = :address 43 | LIMIT 1 44 | `, { 45 | // model: Address, // does properly map dates, but doesn't properly include the name count 46 | // mapToModel: true, 47 | replacements: { address }, 48 | type: QueryTypes.SELECT 49 | }); 50 | 51 | const result: AddressWithNames | null = rows && rows.length ? rows[0] : null; 52 | if (result) { 53 | result.firstseen = new Date(result.firstseen); // convert string to Date 54 | result.names = Number(result.names); // convert string to number 55 | } 56 | return result; 57 | } else { 58 | // Perform the regular lookup 59 | return Address.findOne({ where: { address } }); 60 | } 61 | } 62 | 63 | export async function getAddresses( 64 | limit?: Limit, 65 | offset?: Offset 66 | ): Promise> { 67 | return Address.findAndCountAll({ 68 | limit: sanitiseLimit(limit), 69 | offset: sanitiseOffset(offset) 70 | }); 71 | } 72 | 73 | export async function getRichAddresses( 74 | limit?: Limit, 75 | offset?: Offset 76 | ): Promise> { 77 | return Address.findAndCountAll({ 78 | order: [["balance", "DESC"]], 79 | limit: sanitiseLimit(limit), 80 | offset: sanitiseOffset(offset) 81 | }); 82 | } 83 | 84 | export interface AddressJson { 85 | address: string; 86 | balance: number; 87 | totalin: number; 88 | totalout: number; 89 | firstseen: string; 90 | names?: number; 91 | } 92 | 93 | export function addressToJson(address: AddressWithNames): AddressJson { 94 | return { 95 | address: address.address.toLowerCase(), 96 | balance: address.balance, 97 | totalin: address.totalin, 98 | totalout: address.totalout, 99 | firstseen: address.firstseen.toISOString(), 100 | 101 | // Add the name count, but only if it was requested 102 | ...(address.names !== undefined 103 | ? { names: address.names } 104 | : {}) 105 | }; 106 | } 107 | -------------------------------------------------------------------------------- /src/krist/addresses/lookup.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { QueryTypes, sql } from "@sequelize/core"; 23 | import { Address, db } from "../../database/index.js"; 24 | import { AddressWithNames } from "./index.js"; 25 | 26 | export async function lookupAddresses( 27 | addressList: string[], 28 | fetchNames?: boolean 29 | ): Promise { 30 | if (fetchNames) { 31 | const rows: AddressWithNames[] = await db.query(sql` 32 | SELECT 33 | \`addresses\`.*, 34 | COUNT(\`names\`.\`id\`) AS \`names\` 35 | FROM \`addresses\` 36 | LEFT JOIN \`names\` ON \`addresses\`.\`address\` = \`names\`.\`owner\` 37 | WHERE \`addresses\`.\`address\` IN :addresses 38 | GROUP BY \`addresses\`.\`address\` 39 | ORDER BY \`names\` DESC 40 | `, { 41 | replacements: { addresses: sql.list(addressList) }, 42 | type: QueryTypes.SELECT 43 | }); 44 | 45 | return rows.map(row => { 46 | row.firstseen = new Date(row.firstseen); 47 | row.names = Number(row.names); 48 | return row; 49 | }); 50 | } else { 51 | return Address.findAll({ where: { address: addressList } }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/krist/authLog.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Op, sql } from "@sequelize/core"; 23 | import chalkT from "chalk-template"; 24 | import { Request } from "express"; 25 | import cron from "node-cron"; 26 | import { AuthLog } from "../database/index.js"; 27 | import { getLogDetails } from "../utils/index.js"; 28 | 29 | export type AuthLogType = "auth" | "mining"; 30 | 31 | let job: cron.ScheduledTask | null = null; 32 | 33 | export function initAuthLogCleanup(): void { 34 | // Start the hourly auth log cleaner, and also run it immediately 35 | job = cron.schedule("0 0 * * * *", () => cleanAuthLog().catch(console.error)); 36 | cleanAuthLog().catch(console.error); 37 | } 38 | 39 | export function shutdownAuthLogCleanup(): void { 40 | console.log(chalkT`{cyan [Auth]} Stopping auth log cleaner`); 41 | job?.stop(); 42 | } 43 | 44 | /** For privacy reasons, purge entries from the auth log older than 30 days. */ 45 | async function cleanAuthLog(): Promise { 46 | const destroyed = await AuthLog.destroy({ 47 | where: { 48 | time: { [Op.lte]: sql`NOW() - INTERVAL 30 DAY` } 49 | } 50 | }); 51 | console.log(chalkT`{cyan [Auth]} Purged {bold ${destroyed}} auth log entries`); 52 | } 53 | 54 | export async function logAuth( 55 | req: Request, 56 | address: string, 57 | type: AuthLogType 58 | ): Promise { 59 | const { ip, path, userAgent, libraryAgent, origin, logDetails } = getLogDetails(req); 60 | 61 | if (type === "auth") { 62 | console.log(chalkT`{green [Auth]} ({bold ${path}}) Successful auth on address {bold ${address}} ${logDetails}`); 63 | } 64 | 65 | // Check if there's already a recent log entry with these details. If there 66 | // were any within the last 30 minutes, don't add any new ones. 67 | const existing = await AuthLog.findOne({ 68 | where: { 69 | ip, 70 | address, 71 | time: { [Op.gte]: sql`NOW() - INTERVAL 30 MINUTE` }, 72 | type 73 | } 74 | }); 75 | if (existing) return; 76 | 77 | await AuthLog.create({ 78 | ip, 79 | address, 80 | time: new Date(), 81 | type, 82 | useragent: userAgent, 83 | library_agent: libraryAgent, 84 | origin 85 | }); 86 | } 87 | -------------------------------------------------------------------------------- /src/krist/blocks/lookup.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { InferAttributes, Order, sql } from "@sequelize/core"; 23 | import { Block, Limit, Offset, PaginatedResult } from "../../database/index.js"; 24 | import { sanitiseLimit, sanitiseOffset } from "../../utils/index.js"; 25 | 26 | export async function lookupBlocks( 27 | limit: Limit, 28 | offset: Offset, 29 | orderBy: keyof InferAttributes = "id", 30 | order: "ASC" | "DESC" = "DESC" 31 | ): Promise> { 32 | // This is a hack, but during 2020-03 to 2020-07, there were block hashes lost 33 | // due to a database reconstruction. They are currently marked as NULL in the 34 | // database. In Blocks.getLowestHashes, null hashes are ignored, but here, 35 | // they are still returned. As such, this pushes the nulls to the end of the 36 | // result set if sorting by hash ascending. 37 | const dbOrder: Order = orderBy === "hash" && order === "ASC" 38 | ? [sql`ISNULL(hash)`, ["hash", "ASC"]] 39 | : [[orderBy, order]]; 40 | 41 | return Block.findAndCountAll({ 42 | order: dbOrder, 43 | limit: sanitiseLimit(limit), 44 | offset: sanitiseOffset(offset), 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /src/krist/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { Block } from "../database/index.js"; 24 | 25 | import { redis, rKey } from "../database/redis.js"; 26 | import { MAX_WORK } from "../utils/vars.js"; 27 | 28 | import { isMiningEnabled } from "./switches.js"; 29 | import { getWork, setWork } from "./work.js"; 30 | 31 | export async function initKrist(): Promise { 32 | console.log(chalkT`{bold [Krist]} Loading...`); 33 | 34 | // Check if mining is enabled 35 | if (!await redis.exists(rKey("mining-enabled"))) { 36 | console.log(chalkT`{yellow.bold [Krist]} Note: Initialised with mining disabled.`); 37 | await redis.set(rKey("mining-enabled"), "false"); 38 | } else { 39 | const miningEnabled = await isMiningEnabled(); 40 | if (miningEnabled) console.log(chalkT`{green.bold [Krist]} Mining is enabled.`); 41 | else console.log(chalkT`{red.bold [Krist]} Mining is disabled!`); 42 | } 43 | 44 | // Check if transactions are disabled 45 | if (!await redis.exists(rKey("transactions-enabled"))) { 46 | console.log(chalkT`{yellow.bold [Krist]} Note: Initialised with transactions disabled.`); 47 | await redis.set(rKey("transactions-enabled"), "false"); 48 | } else { 49 | const txEnabled = await redis.get(rKey("transactions-enabled")) === "true"; 50 | if (txEnabled) console.log(chalkT`{green.bold [Krist]} Transactions are enabled.`); 51 | else console.log(chalkT`{red.bold [Krist]} Transactions are disabled!`); 52 | } 53 | 54 | // Check for a genesis block 55 | const lastBlock = await Block.findOne({ order: [["id", "DESC"]] }); 56 | if (!lastBlock) { 57 | console.log(chalkT`{yellow.bold [Krist]} Warning: Genesis block not found. Mining may not behave correctly.`); 58 | } 59 | 60 | // Pre-initialise the work to 100,000 61 | if (!await redis.exists(rKey("work"))) { 62 | const defaultWork = MAX_WORK; 63 | console.log(chalkT`{yellow.bold [Krist]} Warning: Work was not yet set in Redis. It will be initialised to: {green ${defaultWork}}`); 64 | await setWork(defaultWork); 65 | } 66 | console.log(chalkT`{bold [Krist]} Current work: {green ${await getWork()}}`); 67 | } 68 | -------------------------------------------------------------------------------- /src/krist/names/lookup.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { InferAttributes, Op, sql } from "@sequelize/core"; 23 | import { Limit, Name, Offset, PaginatedResult } from "../../database/index.js"; 24 | import { sanitiseLimit, sanitiseOffset } from "../../utils/index.js"; 25 | 26 | export async function lookupNames( 27 | addressList: string[] | undefined, 28 | limit: Limit, 29 | offset: Offset, 30 | orderBy: (keyof InferAttributes) | "transferredOrRegistered" = "name", 31 | order: "ASC" | "DESC" = "ASC" 32 | ): Promise> { 33 | return Name.findAndCountAll({ 34 | order: [[ 35 | // Ordering by `transferred` can return null results and may not be the desirable ordering for the user, so 36 | // `transferredOrRegistered` is an alternative option that falls back to `registered` if `transferred` is null. 37 | orderBy === "transferredOrRegistered" 38 | ? sql`COALESCE(transferred, registered)` 39 | : orderBy, 40 | order 41 | ]], 42 | limit: sanitiseLimit(limit), 43 | offset: sanitiseOffset(offset), 44 | where: addressList ? { owner: {[Op.in]: addressList} } : undefined, 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /src/krist/supply.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Address } from "../database/index.js"; 23 | 24 | export async function getKristSupply(): Promise { 25 | return Address.sum("balance"); 26 | } 27 | -------------------------------------------------------------------------------- /src/krist/switches.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import dayjs from "dayjs"; 23 | import { redis, rKey } from "../database/redis.js"; 24 | import { LAST_BLOCK } from "../utils/vars.js"; 25 | 26 | const cutoff = dayjs(LAST_BLOCK); 27 | 28 | export async function isMiningEnabled(): Promise { 29 | if (dayjs().isAfter(cutoff)) return false; 30 | return (await redis.get(rKey("mining-enabled"))) === "true"; 31 | } 32 | 33 | export async function areTransactionsEnabled(): Promise { 34 | return (await redis.get(rKey("transactions-enabled"))) === "true"; 35 | } 36 | -------------------------------------------------------------------------------- /src/krist/work.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { redis, rKey } from "../database/redis.js"; 23 | import { MAX_WORK } from "../utils/vars.js"; 24 | 25 | export async function getWork(): Promise { 26 | const rawWork = await redis.get(rKey("work")); 27 | return rawWork ? parseInt(rawWork) : MAX_WORK; 28 | } 29 | 30 | export async function getWorkOverTime(): Promise { 31 | return (await redis.lRange(rKey("work-over-time"), 0, 1440)) 32 | .map(i => parseInt(i)) 33 | .reverse(); 34 | } 35 | 36 | export async function setWork(work: number): Promise { 37 | await redis.set(rKey("work"), work.toString()); 38 | } 39 | 40 | let workOverTimeInterval: NodeJS.Timeout; 41 | export function initWorkOverTime(): void { 42 | // Update the work over time every minute 43 | workOverTimeInterval = setInterval(async () => { 44 | await redis.lPush(rKey("work-over-time"), (await getWork()).toString()); 45 | await redis.lTrim(rKey("work-over-time"), 0, 1440); 46 | }, 60 * 1000).unref(); 47 | } 48 | export function shutdownWorkOverTime(): void { 49 | clearInterval(workOverTimeInterval); 50 | } 51 | -------------------------------------------------------------------------------- /src/typings/why-is-node-running.d.ts: -------------------------------------------------------------------------------- 1 | declare module "why-is-node-running" { 2 | export default function whyIsNodeRunning(): void; 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/baseBlockValue.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export function getBaseBlockValue(blockId: number): number { 23 | return blockId >= 222222 ? 1 : (blockId >= 100000 ? 12 : 25); 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/checkEnv.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalk from "chalk"; 23 | 24 | export const REQUIRED_ENV_VARS = ["DB_PASS"]; 25 | 26 | export function checkEnvVars(): void { 27 | const missing = REQUIRED_ENV_VARS.filter(e => !process.env[e]); 28 | if (missing.length) { 29 | console.error(chalk.bold.red("Missing environment variables:")); 30 | console.error(missing.map(e => chalk.red(e)).join(", ")); 31 | process.exit(1); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/utils/crypto.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import crypto from "crypto"; 23 | import { promisify } from "util"; 24 | 25 | const secureBytes = promisify(crypto.randomBytes); 26 | 27 | export function sha256(...inputs: (Uint8Array | string)[]): string { 28 | const hash = crypto.createHash("sha256"); 29 | for (const input of inputs) { 30 | hash.update(input instanceof Uint8Array ? input : input.toString()); 31 | } 32 | return hash.digest("hex"); 33 | } 34 | 35 | export function doubleSha256(input: string): string { 36 | return sha256(sha256(input)); 37 | } 38 | 39 | export function hexToBase36(input: number): string { 40 | const byte = 48 + Math.floor(input / 7); 41 | return String.fromCharCode(byte + 39 > 122 ? 101 : byte > 57 ? byte + 39 : byte); 42 | } 43 | 44 | export function makeV2Address(key: string, addressPrefix = "k"): string { 45 | const chars = ["", "", "", "", "", "", "", "", ""]; 46 | let chain = addressPrefix; 47 | let hash = doubleSha256(key); 48 | 49 | for (let i = 0; i <= 8; i++) { 50 | chars[i] = hash.substring(0, 2); 51 | hash = doubleSha256(hash); 52 | } 53 | 54 | for (let i = 0; i <= 8;) { 55 | const index = parseInt(hash.substring(2 * i, 2 + (2 * i)), 16) % 9; 56 | 57 | if (chars[index] === "") { 58 | hash = sha256(hash); 59 | } else { 60 | chain += hexToBase36(parseInt(chars[index], 16)); 61 | chars[index] = ""; 62 | i++; 63 | } 64 | } 65 | 66 | return chain; 67 | } 68 | 69 | export async function generateWebSocketToken(): Promise { 70 | // NOTE: These used to be UUIDs, so we use 18 bytes here to maintain 71 | // compatibility with anything that may expect exactly 36 characters. 72 | return (await secureBytes(18)).toString("hex"); 73 | } 74 | -------------------------------------------------------------------------------- /src/utils/fileExists.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import fs from "fs"; 23 | 24 | export const fileExists = (f: string): Promise => 25 | fs.promises.access(f, fs.constants.F_OK).then(() => true).catch(() => false); 26 | -------------------------------------------------------------------------------- /src/utils/format.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export function padDigits(n: number, digits: number): string { 23 | return new Array(Math.max(digits - String(n).length + 1, 0)).join("0") + n; 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export * from "./baseBlockValue.js"; 23 | export * from "./checkEnv.js"; 24 | export * from "./crypto.js"; 25 | export * from "./fileExists.js"; 26 | export * from "./format.js"; 27 | export * from "./legacyWork.js"; 28 | export * from "./log.js"; 29 | export * from "./lut.js"; 30 | export * from "./validation.js"; 31 | export * from "./validationKrist.js"; 32 | -------------------------------------------------------------------------------- /src/utils/legacyWork.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export function getLegacyWork(id: number): number | null { 23 | // Early return for all existing blocks 24 | if (id >= 5000) return null; 25 | 26 | if (id >= 1 && id < 501) return 400000000000; 27 | if (id >= 501 && id < 541) return 381274937337; 28 | if (id >= 541 && id < 546) return 350000000000; 29 | if (id >= 546 && id < 549) return 400000000000; 30 | if (id >= 549 && id < 554) return 300000000000; 31 | if (id >= 554 && id < 635) return 288365888229; 32 | if (id >= 635 && id < 891) return 58365888229; 33 | if (id >= 891 && id < 936) return 6000000000; 34 | if (id >= 936 && id < 974) return 400000000000; 35 | if (id >= 974 && id < 979) return 100000000000; 36 | if (id >= 979 && id < 1083) return 400000000000; 37 | if (id >= 1083 && id < 1149) return 100000000000; 38 | if (id >= 1149 && id < 1165) return 10000000000; 39 | if (id >= 1165 && id < 1171) return 5000000000; 40 | if (id >= 1171 && id < 1172) return 500000000; 41 | if (id >= 1172 && id < 1178) return 5000000000; 42 | if (id >= 1178 && id < 1355) return 2000000000000; 43 | if (id >= 1355 && id < 1390) return 200000000000; 44 | if (id >= 1390 && id < 2486) return 20000000000; 45 | if (id >= 2486 && id < 2640) return 400000000000; 46 | if (id >= 2640 && id < 2667) return 300000000000; 47 | if (id >= 2667 && id < 2700) return 3000000000; 48 | if (id >= 2700 && id < 2743) return 10000000000; 49 | if (id >= 2743 && id < 2773) return 8000000000; 50 | if (id >= 2773 && id < 2795) return 5000000000; 51 | if (id >= 2795 && id < 2812) return 3000000000; 52 | if (id >= 2812 && id < 2813) return 1000000000; 53 | if (id >= 2813 && id < 2936) return 400000000000; 54 | if (id >= 2936 && id < 2942) return 4000000000; 55 | if (id >= 2942 && id < 2972) return 8000000000; 56 | if (id >= 2972 && id < 2989) return 2000000000; 57 | if (id >= 2989 && id < 2990) return 100000000; 58 | if (id >= 2990 && id < 2998) return 500000000; 59 | if (id >= 2998 && id < 3000) return 200000000; 60 | if (id >= 3000 && id < 3003) return 100000000; 61 | if (id >= 3003 && id < 3005) return 50000000; 62 | if (id >= 3005 && id < 3006) return 23555120; 63 | if (id >= 3006 && id < 3018) return 53555120; 64 | if (id >= 3018 && id < 3029) return 20000000; 65 | if (id >= 3029 && id < 3089) return 400000000000; 66 | if (id >= 3089 && id < 3096) return 20000000; 67 | if (id >= 3096 && id < 3368) return 19875024; 68 | if (id >= 3368 && id < 4097) return 10875024; 69 | if (id >= 4097 && id < 5000) return 8750240; 70 | 71 | return null; 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { Request } from "express"; 24 | import { sanitiseOrigin, sanitiseUserAgent } from "./validation.js"; 25 | 26 | export interface ReqDetails { 27 | origin?: string; 28 | userAgent?: string; 29 | libraryAgent?: string; 30 | } 31 | 32 | export interface LogDetails extends ReqDetails { 33 | ip: string | undefined; 34 | path: "WS" | string; 35 | logDetails: string; 36 | } 37 | 38 | export function getReqDetails(req?: Request): ReqDetails { 39 | if (!req) return { userAgent: undefined, origin: undefined }; 40 | 41 | const userAgent = sanitiseUserAgent(req.header("User-Agent")); 42 | const libraryAgent = sanitiseUserAgent(req.header("Library-Agent")); 43 | const origin = sanitiseOrigin(req.header("Origin")); 44 | return { userAgent, libraryAgent, origin }; 45 | } 46 | 47 | export function getLogDetails(req: Request): LogDetails { 48 | const ip = req.ip; 49 | const { userAgent, libraryAgent, origin } = getReqDetails(req); 50 | const path = req.path && req.path.startsWith("/.websocket//") ? "WS" : req.path; 51 | const logDetails = chalkT`(ip: {bold ${ip}} origin: {bold ${origin}} useragent: {bold ${userAgent}} library agent: {bold ${libraryAgent}})`; 52 | 53 | return { ip, origin, userAgent, libraryAgent, path, logDetails }; 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/lut.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export function lut(data: T[]): Record { 23 | const out: any = {}; 24 | for (const v of data) out[v] = true; 25 | return out; 26 | } 27 | 28 | export function ulut(data?: T[]): Record | undefined { 29 | if (!data || data.length === 0) return; 30 | return lut(data); 31 | } 32 | 33 | export function nlut(data: T[]): Record { 34 | const out: any = {}; 35 | for (const v of data) out[v] = 1; 36 | return out; 37 | } 38 | 39 | export function unlut(data?: T[]): Record | undefined { 40 | if (!data || data.length === 0) return; 41 | return nlut(data); 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/rateLimit.ts: -------------------------------------------------------------------------------- 1 | import rateLimit, { RateLimitRequestHandler } from "express-rate-limit"; 2 | import promClient from "prom-client"; 3 | import RedisStore from "rate-limit-redis"; 4 | import { BurstyRateLimiter, RateLimiterRedis } from "rate-limiter-flexible"; 5 | import { redis } from "../database/redis.js"; 6 | import { REDIS_PREFIX, TEST } from "./vars.js"; 7 | 8 | const promWebserverRateLimit = new promClient.Counter({ 9 | name: "krist_webserver_rate_limited", 10 | help: "Total number of requests that were rate limited." 11 | }); 12 | const promTransactionRateLimit = new promClient.Counter({ 13 | name: "krist_transactions_rate_limited", 14 | help: "Total number of transactions that were rate limited." 15 | }); 16 | 17 | export const webserverRateLimiter = (): RateLimitRequestHandler => rateLimit({ 18 | windowMs: 60000, 19 | limit: 180, 20 | standardHeaders: true, 21 | 22 | handler: (req, res, next, options) => { 23 | promWebserverRateLimit.inc(); 24 | res.status(options.statusCode).json({ ok: false, error: "rate_limit_hit" }); 25 | }, 26 | 27 | store: new RedisStore({ 28 | prefix: REDIS_PREFIX + "webserverRateLimiter:", 29 | sendCommand: (...args: string[]) => redis.sendCommand(args) 30 | }) 31 | }); 32 | 33 | const txIpRateLimiter = new BurstyRateLimiter( 34 | new RateLimiterRedis({ 35 | storeClient: redis, 36 | keyPrefix: REDIS_PREFIX + "txIpRateLimiter:", 37 | points: 3, // 3 transactions 38 | duration: 1, // per 1 second by IP 39 | }), 40 | new RateLimiterRedis({ 41 | storeClient: redis, 42 | keyPrefix: REDIS_PREFIX + "txIpRateLimiterBurst:", 43 | points: 12, // 12 transactions 44 | duration: 5, // per 5 seconds by IP 45 | }) 46 | ); 47 | 48 | const txAddressRateLimiter = new BurstyRateLimiter( 49 | new RateLimiterRedis({ 50 | storeClient: redis, 51 | keyPrefix: REDIS_PREFIX + "txAddressRateLimiter:", 52 | points: 3, // 3 transactions 53 | duration: 1, // per 1 second by address 54 | }), 55 | new RateLimiterRedis({ 56 | storeClient: redis, 57 | keyPrefix: REDIS_PREFIX + "txAddressRateLimiterBurst:", 58 | points: 12, // 12 transactions 59 | duration: 5, // per 5 seconds by address 60 | }) 61 | ); 62 | 63 | export async function checkTxRateLimits( 64 | ip: string | undefined, 65 | address: string, 66 | cost = 1 67 | ): Promise { 68 | if (TEST) return true; 69 | 70 | try { 71 | await Promise.all([ 72 | ip ? txIpRateLimiter.consume(ip, cost) : Promise.resolve(), 73 | txAddressRateLimiter.consume(address, cost) 74 | ]); 75 | 76 | return true; 77 | } catch (err) { 78 | console.error("Rate limit exceeded", ip, address, err); 79 | promTransactionRateLimit.inc(); 80 | return false; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/utils/validation.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Literal } from "@sequelize/core"; 23 | import { isNaN } from "lodash-es"; 24 | import { db, Limit, Offset } from "../database/index.js"; 25 | import { ErrorInvalidParameter } from "../errors/index.js"; 26 | 27 | /** 28 | * @apiDefine Limit 29 | * @apiQuery {Number{1-1000}} [limit=50] The maximum amount of results to 30 | * return. Must be between 1 and 1000. 31 | */ 32 | 33 | /** 34 | * @apiDefine Offset 35 | * @apiQuery {Number} [offset=0] The amount to offset the results, useful to 36 | * paginate results, and in conjunction with `limit`. 37 | */ 38 | 39 | /** 40 | * @apiDefine LimitOffset 41 | * @apiUse Limit 42 | * @apiUse Offset 43 | */ 44 | 45 | export async function validateLimit(limit: Limit): Promise { 46 | if ((limit && isNaN(limit)) 47 | || (limit && ((typeof limit === "number" && limit <= 0) 48 | || (typeof limit === "string" && parseInt(limit) <= 0)))) { 49 | throw new ErrorInvalidParameter("limit"); 50 | } 51 | } 52 | 53 | export async function validateOffset(offset: Offset): Promise { 54 | if ((offset && isNaN(offset)) 55 | || (offset && ((typeof offset === "number" && offset < 0) 56 | || (typeof offset === "string" && parseInt(offset) < 0)))) { 57 | throw new ErrorInvalidParameter("offset"); 58 | } 59 | } 60 | 61 | export async function validateLimitOffset( 62 | limit: Limit, 63 | offset: Offset 64 | ): Promise { 65 | await validateLimit(limit); 66 | await validateOffset(offset); 67 | } 68 | 69 | export function sanitiseLimit( 70 | limit: Limit, 71 | def = 50, 72 | max = 1000 73 | ): number { 74 | if ( 75 | limit === undefined 76 | || limit === null 77 | || limit === "" 78 | || (typeof limit === "string" && limit.trim() === "") 79 | || isNaN(parseInt(limit as string)) 80 | ) { 81 | return def; 82 | } 83 | 84 | const limitNumber = typeof limit === "string" ? parseInt(limit) : limit; 85 | 86 | return Math.min( 87 | limitNumber < 0 ? def : limitNumber, 88 | max 89 | ); 90 | } 91 | 92 | export function sanitiseOffset( 93 | offset: Offset 94 | ): number | undefined { 95 | if (!offset) return undefined; 96 | return typeof offset === "string" ? parseInt(offset) : offset; 97 | } 98 | 99 | export function sanitiseLike(query?: string | null): Literal { 100 | if (!query || typeof query !== "string") 101 | throw new Error("invalid like"); 102 | 103 | const inputRaw = query.replace(/([_%\\])/g, "\\$1"); 104 | const inputEscaped = db.escape(`%${inputRaw}%`); 105 | return db.literal(inputEscaped); 106 | } 107 | 108 | export function sanitiseUserAgent( 109 | userAgent?: string | null 110 | ): string | undefined { 111 | if (!userAgent || typeof userAgent !== "string") return; 112 | if (userAgent.length > 255) return userAgent.substring(0, 255); 113 | return userAgent; 114 | } 115 | export const sanitiseOrigin = sanitiseUserAgent; 116 | -------------------------------------------------------------------------------- /src/utils/validationKrist.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | export const ADDRESS_RE = /^(?:k[a-z0-9]{9}|[a-f0-9]{10})$/; 23 | export const ADDRESS_RE_V2 = /^k[a-z0-9]{9}$/; 24 | export const ADDRESS_LIST_RE = /^(?:k[a-z0-9]{9}|[a-f0-9]{10})(?:,(?:k[a-z0-9]{9}|[a-f0-9]{10}))*$/; 25 | export const NAME_RE = /^[a-z0-9]{1,64}$/; 26 | export const NAME_FETCH_RE = /^(?:xn--)?[a-z0-9]{1,64}$/i; 27 | export const NAME_A_RECORD_RE = /^[^\s.?#].[^\s]*$/i; 28 | export const NAME_META_RE = /^(?:([a-z0-9-_]{1,32})@)?([a-z0-9]{1,64})\.kst$/i; 29 | export const METANAME_METADATA_RE = /^(?:([a-z0-9-_]{1,32})@)?([a-z0-9]{1,64})\.kst/i; 30 | export const REQUEST_ID_RE = /^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/; 31 | 32 | export function isValidKristAddress(address: string, v2Only?: boolean): boolean { 33 | return v2Only ? ADDRESS_RE_V2.test(address) : ADDRESS_RE.test(address); 34 | } 35 | 36 | export function isValidKristAddressList(addressList: string): boolean { 37 | return ADDRESS_LIST_RE.test(addressList); 38 | } 39 | 40 | export function isValidName(name: string, fetching?: boolean): boolean { 41 | const re = fetching ? NAME_FETCH_RE : NAME_RE; 42 | name = name.toLowerCase(); 43 | return re.test(name) && name.length > 0 && name.length < 65; 44 | } 45 | 46 | export function isValidARecord(a: string): boolean { 47 | return !!a && a.length > 0 && a.length <= 255 && NAME_A_RECORD_RE.test(a); 48 | } 49 | 50 | export function stripNameSuffix(name: string): string { 51 | if (!name) return ""; 52 | 53 | // TODO: Support custom name suffixes (see KristWeb v2 code for safe RegExp 54 | // compilation and memoization) 55 | return name.replace(/\.kst$/i, ""); 56 | } 57 | -------------------------------------------------------------------------------- /src/utils/whatsNew.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import whatsNewData from "../../whats-new.json" with { type: "json" }; 23 | 24 | export interface WhatsNewEntry { 25 | new?: boolean; 26 | commitHash: string; 27 | date: string; // ISO-8601 28 | body: string; 29 | authorUsername?: string; 30 | authorName?: string; 31 | } 32 | 33 | export const whatsNew = whatsNewData.new as WhatsNewEntry[]; 34 | -------------------------------------------------------------------------------- /src/webserver/prometheus.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { Express, Request } from "express"; 24 | import basicAuth from "express-basic-auth"; 25 | import client from "prom-client"; 26 | import { PROMETHEUS_PASSWORD, USE_PROMETHEUS } from "../utils/vars.js"; 27 | 28 | client.collectDefaultMetrics(); 29 | 30 | const up = new client.Gauge({ 31 | name: "krist_up", 32 | help: "Whether the Krist server is running." 33 | }); 34 | up.set(1); 35 | 36 | const debug = new client.Gauge({ 37 | name: "krist_debug", 38 | help: "Whether the Krist server is in debug mode." 39 | }); 40 | debug.set(process.env.NODE_ENV !== "production" ? 1 : 0); 41 | 42 | export function initPrometheus(app: Express): void { 43 | // no-op if prometheus is disabled 44 | if (!USE_PROMETHEUS) return; 45 | 46 | // protect the metrics endpoint if a password was specified 47 | if (PROMETHEUS_PASSWORD) { 48 | app.use("/metrics", basicAuth({ 49 | users: { "prometheus": PROMETHEUS_PASSWORD }, 50 | 51 | unauthorizedResponse(req: Request) { 52 | console.log(chalkT`{red [Webserver]} Unauthorized access on Prometheus endpoint from {bold ${req.ip}}!`); 53 | return ""; 54 | } 55 | })); 56 | } 57 | 58 | app.use("/metrics", async (req, res) => { 59 | try { 60 | res.set("Content-Type", client.register.contentType); 61 | res.end(await client.register.metrics()); 62 | } catch (err) { 63 | res.status(500).end(err); 64 | } 65 | }); 66 | 67 | console.log(chalkT`{cyan [Webserver]} Prometheus is enabled!`); 68 | } 69 | -------------------------------------------------------------------------------- /src/webserver/routes/homepage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import * as handlebars from "handlebars"; 24 | import { marked } from "marked"; 25 | import { baseUrl } from "marked-base-url"; 26 | import sanitizeHtml from "sanitize-html"; 27 | import { getCommits } from "../../utils/git.js"; 28 | import { PUBLIC_URL } from "../../utils/vars.js"; 29 | import { whatsNew } from "../../utils/whatsNew.js"; 30 | 31 | marked.use(baseUrl(PUBLIC_URL)); 32 | marked.use({ 33 | async: false 34 | }); 35 | 36 | export default (): Router => { 37 | const router = Router(); 38 | 39 | router.get("/", async (req, res) => { 40 | let commits; 41 | try { 42 | commits = await getCommits(); 43 | } catch (err) { 44 | console.error("Error fetching git log:", err); 45 | } 46 | 47 | res.header("Content-Type", "text/html"); 48 | res.render("home", { 49 | commits, 50 | whatsNew, 51 | 52 | helpers: { 53 | // Render markdown (sanitised by sanitize-html) 54 | marked(data: any) { 55 | return new (handlebars as any).default.SafeString(sanitizeHtml(marked.parse(data) as string)); 56 | } 57 | } 58 | }); 59 | }); 60 | 61 | return router; 62 | }; 63 | -------------------------------------------------------------------------------- /src/webserver/routes/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | 24 | import routerAddresses from "./addresses.js"; 25 | import routerBlocks from "./blocks.js"; 26 | 27 | import routerHomepage from "./homepage.js"; 28 | import routerLogin from "./login.js"; 29 | 30 | import routerLookup from "./lookup/index.js"; 31 | import routerMotd from "./motd.js"; 32 | import routerNames from "./names.js"; 33 | import routerSearch from "./search/index.js"; 34 | import routerSubmission from "./submission.js"; 35 | import routerSupply from "./supply.js"; 36 | import routerTransactions from "./transactions.js"; 37 | import routerV2 from "./v2.js"; 38 | import routerWalletVersion from "./walletVersion.js"; 39 | import routerWebsockets from "./websockets.js"; 40 | import routerWhatsNew from "./whatsNew.js"; 41 | import routerWork from "./work.js"; 42 | 43 | // Primary API router 44 | export default (): Router => { 45 | const router = Router(); 46 | 47 | router.use(routerAddresses()); 48 | router.use(routerBlocks()); 49 | router.use(routerLogin()); 50 | router.use(routerMotd()); 51 | router.use(routerNames()); 52 | router.use(routerSubmission()); 53 | router.use(routerSupply()); 54 | router.use(routerTransactions()); 55 | router.use(routerV2()); 56 | router.use(routerWalletVersion()); 57 | router.use(routerWebsockets()); 58 | router.use(routerWhatsNew()); 59 | router.use(routerWork()); 60 | 61 | router.use("/lookup", routerLookup()); 62 | router.use("/search", routerSearch()); 63 | 64 | // Run the homepage route last so the Legacy API can take hold of / 65 | router.use(routerHomepage()); 66 | 67 | return router; 68 | }; 69 | -------------------------------------------------------------------------------- /src/webserver/routes/login.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { ErrorInvalidParameter, ErrorMissingParameter } from "../../errors/index.js"; 24 | import { verifyAddress } from "../../krist/addresses/verify.js"; 25 | 26 | export default (): Router => { 27 | const router = Router(); 28 | 29 | /** 30 | * @api {post} /login Authenticate an address 31 | * @apiName Login 32 | * @apiGroup MiscellaneousGroup 33 | * @apiVersion 2.7.0 34 | * 35 | * @apiBody {String} privatekey The privatekey to auth with 36 | * 37 | * @apiSuccessExample {json} Success, Authed 38 | * { 39 | * "ok": true, 40 | * "authed": true, 41 | * "address": "kre3w0i79j" 42 | * } 43 | * 44 | * @apiSuccessExample {json} Success, Auth Failed 45 | * { 46 | * "ok": true, 47 | * "authed": false 48 | * } 49 | */ 50 | router.post("/login", async (req, res) => { 51 | if (!req.body.privatekey) throw new ErrorMissingParameter("privatekey"); 52 | 53 | // 2021: v1 addresses have been removed 54 | if (req.query.v === "1") throw new ErrorInvalidParameter("v"); 55 | 56 | const results = await verifyAddress(req, req.body.privatekey); 57 | 58 | if (results.authed) { 59 | return res.json({ 60 | ok: true, 61 | authed: true, 62 | address: results.address.address 63 | }); 64 | } else { 65 | return res.json({ 66 | ok: true, 67 | authed: false 68 | }); 69 | } 70 | }); 71 | 72 | return router; 73 | }; 74 | -------------------------------------------------------------------------------- /src/webserver/routes/lookup/blocks.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { blockToJson } from "../../../krist/blocks/index.js"; 24 | import { lookupBlocks } from "../../../krist/blocks/lookup.js"; 25 | import { BLOCK_FIELDS, LookupQuery } from "./index.js"; 26 | import { validateLimit, validateOffset, validateOrder, validateOrderBy } from "./utils.js"; 27 | 28 | export default (): Router => { 29 | const router = Router(); 30 | 31 | router.get("/blocks", async (req: LookupQuery, res) => { 32 | // Query filtering parameters 33 | const limit = validateLimit(req.query.limit); 34 | const offset = validateOffset(req.query.offset); 35 | const orderBy = validateOrderBy(BLOCK_FIELDS, req.query.orderBy); 36 | const order = validateOrder(req.query.order); 37 | 38 | // Perform the query 39 | // NOTE: `height` is replaced with `id` to maintain compatibility with what the API typically returns for block 40 | // objects. 41 | // NOTE: `time` is replaced with `id` as `time` is typically not indexed. While blocks are not _guaranteed_ to be 42 | // monotonic, they generally are, so this is a worthwhile performance tradeoff. 43 | const { rows, count } = await lookupBlocks( 44 | limit, 45 | offset, 46 | orderBy === "height" 47 | ? "id" 48 | : (orderBy === "time" ? "id" : orderBy), 49 | order 50 | ); 51 | 52 | return res.json({ 53 | ok: true, 54 | count: rows.length, 55 | total: count, 56 | blocks: rows.map(blockToJson) 57 | }); 58 | }); 59 | 60 | return router; 61 | }; 62 | -------------------------------------------------------------------------------- /src/webserver/routes/lookup/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { ReqQuery } from "../../index.js"; 24 | import routerLookupAddresses from "./addresses.js"; 25 | import routerLookupBlocks from "./blocks.js"; 26 | import routerLookupNames from "./names.js"; 27 | import routerLookupTransactions from "./transactions.js"; 28 | 29 | // Fair tradeoff between flexibility and parameter limitations 30 | export const ADDRESS_LIST_LIMIT = 128; 31 | 32 | // Valid fields to order block lookups by 33 | export type BlockLookupFields = "height" | "address" | "hash" | "value" | 34 | "time" | "difficulty"; 35 | export const BLOCK_FIELDS: BlockLookupFields[] = ["height", "address", "hash", 36 | "value", "time", "difficulty"]; 37 | // Valid fields to order transaction lookups by 38 | export type TransactionLookupFields = "id" | "from" | "to" | "value" | "time" | 39 | "sent_name" | "sent_metaname" 40 | export const TRANSACTION_FIELDS: TransactionLookupFields[] = ["id", "from", 41 | "to", "value", "time", "sent_name", "sent_metaname"]; 42 | // Valid fields to order name lookups by 43 | export type NameLookupFields = "name" | "owner" | "original_owner" | 44 | "registered" | "updated" | "transferred" | "transferredOrRegistered" | "a" | 45 | "unpaid"; 46 | export const NAME_FIELDS: NameLookupFields[] = ["name", "owner", 47 | "original_owner", "registered", "updated", "transferred", 48 | "transferredOrRegistered", "a", "unpaid"]; 49 | 50 | export type LookupQuery = ReqQuery<{ 51 | limit?: string; 52 | offset?: string; 53 | orderBy?: string; 54 | order?: string; 55 | } & T>; 56 | 57 | /** 58 | * @apiDefine LookupGroup Lookup API 59 | * 60 | * Advanced bulk lookup queries designed for KristWeb v2. 61 | */ 62 | export default (): Router => { 63 | const router = Router(); 64 | 65 | router.use(routerLookupAddresses()); 66 | router.use(routerLookupBlocks()); 67 | router.use(routerLookupNames()); 68 | router.use(routerLookupTransactions()); 69 | 70 | return router; 71 | }; 72 | 73 | export * from "./utils.js"; 74 | -------------------------------------------------------------------------------- /src/webserver/routes/lookup/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Limit, Offset } from "../../../database/index.js"; 23 | import { ErrorInvalidParameter } from "../../../errors/index.js"; 24 | import { isValidKristAddressList } from "../../../utils/index.js"; 25 | import { ADDRESS_LIST_LIMIT } from "./index.js"; 26 | 27 | /** Validate a comma-separated list of addresses, returning an array of them 28 | * if it is valid, or throwing an error if it is not. */ 29 | export function validateAddressList( 30 | addressList: string 31 | ): string[] { 32 | // If it doesn't match the address list regex, error 33 | if (!isValidKristAddressList(addressList)) 34 | throw new ErrorInvalidParameter("addresses"); 35 | 36 | // Deserialize, clean up, and deduplicate address list 37 | const addresses = [...new Set(addressList.trim().toLowerCase().split(","))]; 38 | 39 | // Check that they didn't supply too many addresses 40 | if (addresses.length > ADDRESS_LIST_LIMIT) 41 | throw new ErrorInvalidParameter("addresses"); 42 | 43 | return addresses; 44 | } 45 | 46 | export function validateOrderBy( 47 | validFields: T[], 48 | order?: string 49 | ): T | undefined { 50 | // Ignore unsupplied parameter 51 | if (order === undefined) return; 52 | 53 | if (typeof order !== "string" || !validFields.includes(order as T)) 54 | throw new ErrorInvalidParameter("orderBy"); 55 | 56 | return order as T; 57 | } 58 | 59 | export function validateOrder(order?: string): "ASC" | "DESC" | undefined { 60 | // Ignore unsupplied parameter 61 | if (order === undefined) return "ASC"; 62 | 63 | if (typeof order !== "string" 64 | || (order.toUpperCase() !== "ASC" && order.toUpperCase() !== "DESC")) 65 | throw new ErrorInvalidParameter("order"); 66 | 67 | return order.toUpperCase() as "ASC" | "DESC"; 68 | } 69 | 70 | export function validateLimit(limit: Limit): number | undefined { 71 | // Ignore unsupplied parameter 72 | if (limit === undefined || limit === null) return; 73 | 74 | // Convert to int 75 | limit = typeof limit === "string" ? parseInt(limit) : limit; 76 | 77 | // Validate range 78 | if (isNaN(limit) || (limit && limit <= 0)) 79 | throw new ErrorInvalidParameter("limit"); 80 | 81 | return limit; 82 | } 83 | 84 | export function validateOffset(offset: Offset): number | undefined { 85 | // Ignore unsupplied parameter 86 | if (offset === undefined || offset === null) return; 87 | 88 | // Convert to int 89 | offset = typeof offset === "string" ? parseInt(offset) : offset; 90 | 91 | // Validate range 92 | if (isNaN(offset) || (offset && offset <= 0)) 93 | throw new ErrorInvalidParameter("offset"); 94 | 95 | return offset; 96 | } 97 | -------------------------------------------------------------------------------- /src/webserver/routes/search/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { ErrorInvalidParameter, ErrorMissingParameter } from "../../../errors/index.js"; 23 | import { isValidKristAddress, isValidName, stripNameSuffix } from "../../../utils/index.js"; 24 | import { ReqSearchQuery, SearchQueryMatch } from "./index.js"; 25 | 26 | export function parseQuery(query: string): SearchQueryMatch { 27 | const matchAddress = isValidKristAddress(query); 28 | 29 | const strippedName = stripNameSuffix(query); 30 | const matchName = !!strippedName && isValidName(strippedName, true); 31 | 32 | const cleanId = parseInt(query.replace(/\W/g, "")); 33 | const hasId = !isNaN(cleanId); 34 | const matchBlock = hasId; 35 | const matchTransaction = hasId; 36 | 37 | return { 38 | originalQuery: query, 39 | 40 | matchAddress, 41 | matchName, 42 | matchBlock, 43 | matchTransaction, 44 | 45 | strippedName, 46 | hasID: hasId, 47 | ...(hasId ? { cleanID: cleanId } : {}) 48 | }; 49 | } 50 | 51 | export function validateQuery(req: ReqSearchQuery): string { 52 | const query = req.query.q; 53 | if (!query) throw new ErrorMissingParameter("q"); 54 | if (typeof query !== "string") throw new ErrorInvalidParameter("q"); 55 | 56 | const trimmedQuery = query.trim(); 57 | if (trimmedQuery.length > 256) throw new ErrorInvalidParameter("q"); 58 | 59 | return trimmedQuery; 60 | } 61 | -------------------------------------------------------------------------------- /src/webserver/routes/supply.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { getKristSupply } from "../../krist/supply.js"; 24 | 25 | export default (): Router => { 26 | const router = Router(); 27 | 28 | // =========================================================================== 29 | // API v2 30 | // =========================================================================== 31 | /** 32 | * @api {get} /supply Get the money supply 33 | * @apiName GetMoneySupply 34 | * @apiGroup MiscellaneousGroup 35 | * @apiVersion 2.0.0 36 | * 37 | * @apiDescription Returns the amount of Krist currently in circulation. 38 | * 39 | * @apiSuccess {Number} money_supply The amount of Krist in circulation. 40 | * 41 | * @apiSuccessExample {json} Success 42 | * { 43 | * "ok": true, 44 | * "money_supply": 1013359534 45 | * } 46 | */ 47 | router.get("/supply", async (req, res) => { 48 | const supply = await getKristSupply(); 49 | 50 | res.json({ 51 | ok: true, 52 | money_supply: supply 53 | }); 54 | }); 55 | 56 | // =========================================================================== 57 | // Legacy API 58 | // =========================================================================== 59 | router.get("/", async (req, res, next) => { 60 | if (req.query.getmoneysupply !== undefined) { 61 | res.send((await getKristSupply()).toString()); 62 | return; 63 | } 64 | 65 | next(); 66 | }); 67 | 68 | return router; 69 | }; 70 | -------------------------------------------------------------------------------- /src/webserver/routes/v2.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { ErrorInvalidParameter, ErrorMissingParameter } from "../../errors/index.js"; 24 | import { makeV2Address } from "../../utils/index.js"; 25 | import { ReqQuery } from "../index.js"; 26 | 27 | export default (): Router => { 28 | const router = Router(); 29 | 30 | // =========================================================================== 31 | // API v2 32 | // =========================================================================== 33 | /** 34 | * @api {post} /v2 Get v2 address from a private key 35 | * @apiName MakeV2Address 36 | * @apiGroup MiscellaneousGroup 37 | * @apiVersion 2.0.0 38 | * 39 | * @apiBody {String} privatekey The private key to turn into 40 | * an address 41 | * 42 | * @apiSuccess {String} address The address from the private key 43 | * 44 | * @apiSuccessExample {json} Success 45 | * { 46 | * "ok": true, 47 | * "address": "kre3w0i79j" 48 | * } 49 | */ 50 | router.post("/v2", (req, res) => { 51 | if (!req.body.privatekey) throw new ErrorMissingParameter("privatekey"); 52 | if (typeof req.body.privatekey !== "string") 53 | throw new ErrorInvalidParameter("privatekey"); 54 | 55 | res.json({ 56 | ok: true, 57 | address: makeV2Address(req.body.privatekey) 58 | }); 59 | }); 60 | 61 | // =========================================================================== 62 | // Legacy API 63 | // =========================================================================== 64 | router.get("/", (req: ReqQuery<{ 65 | v2?: string; 66 | }>, res, next) => { 67 | if (req.query.v2) { 68 | return res.send(makeV2Address(req.query.v2)); 69 | } 70 | 71 | next(); 72 | }); 73 | 74 | return router; 75 | }; 76 | -------------------------------------------------------------------------------- /src/webserver/routes/walletVersion.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { WALLET_VERSION } from "../../utils/vars.js"; 24 | 25 | export default (): Router => { 26 | const router = Router(); 27 | 28 | // =========================================================================== 29 | // API v2 30 | // =========================================================================== 31 | /** 32 | * @api {get} /walletversion Get latest KristWallet version 33 | * @apiName GetWalletVersion 34 | * @apiGroup MiscellaneousGroup 35 | * @apiVersion 2.0.0 36 | * 37 | * @apiSuccess {Number} walletVersion The latest KristWallet version. 38 | * 39 | * @apiSuccessExample {json} Success 40 | * { 41 | * "ok": true, 42 | * "walletVersion": 14 43 | * } 44 | */ 45 | router.get("/walletversion", (req, res) => { 46 | res.json({ 47 | ok: true, 48 | walletVersion: WALLET_VERSION 49 | }); 50 | }); 51 | 52 | // =========================================================================== 53 | // Legacy API 54 | // =========================================================================== 55 | router.get("/", (req, res, next) => { 56 | if (req.query.getwalletversion !== undefined) { 57 | return res.send(WALLET_VERSION.toString()); 58 | } 59 | 60 | next(); 61 | }); 62 | 63 | return router; 64 | }; 65 | -------------------------------------------------------------------------------- /src/webserver/routes/whatsNew.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { getCommits } from "../../utils/git.js"; 24 | import { whatsNew } from "../../utils/whatsNew.js"; 25 | 26 | export default (): Router => { 27 | const router = Router(); 28 | 29 | /** 30 | * @api {get} /whatsnew Get recent changes to the Krist project 31 | * @apiName GetWhatsNew 32 | * @apiGroup MiscellaneousGroup 33 | * @apiVersion 3.0.0 34 | * 35 | * @apiSuccess {Object[]} commits The 10 most recent Git commits 36 | * @apiSuccess {String} [commits.type] The 37 | * [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 38 | * type of this commit, if available. Usually `fix` or `feat`. 39 | * @apiSuccess {String} commits.subject The subject message of this commit 40 | * @apiSuccess {String} commits.body The full commit message, including the 41 | * type and subject 42 | * @apiSuccess {String} commits.hash The hash of this commit 43 | * @apiSuccess {String} commits.authorName The name of the author of this 44 | * commit 45 | * @apiSuccess {String} commits.authorEmail The e-mail of the author of this 46 | * commit 47 | * @apiSuccess {String} commits.authorDate The date this commit was authored 48 | * @apiSuccess {String} commits.authorDateRel The date this commit was 49 | * authored, as a string relative to the current time (e.g. `6 days ago`) 50 | * @apiSuccess {String} [commits.avatar] The URL of the author's avatar on 51 | * GitHub, if available. 52 | * 53 | * @apiSuccessExample {json} Success 54 | * { 55 | * "ok": true, 56 | * "commits": [ 57 | * { 58 | * "type": "feat", 59 | * "subject": "add names.transferred to lookup API", 60 | * "body": "feat: add names.transferred to lookup API\n", 61 | * "hash": "dc64fb2ee7750f063f2c5e0d0fffc11571f3549a", 62 | * "authorName": "Drew Edwards", 63 | * "authorEmail": "name@example.com", 64 | * "authorDate": "2022-04-07T23:11:06.000Z", 65 | * "authorDateRel": "6 days ago", 66 | * "avatar": null 67 | * }, 68 | * ... 69 | * ], 70 | * "whatsNew": [ 71 | * { 72 | * "commitHash": "5fed5626e6ce0e24c6a4a3c574ce73d5a6cca69e", 73 | * "date": "2022-04-07T23:24:01+01:00", 74 | * "new": true, 75 | * "body": "Names have a new `transferred` field that returns when they were last transferred to a new owner." 76 | * }, 77 | * ... 78 | * ] 79 | * } 80 | */ 81 | router.get("/whatsnew", async (req, res) => { 82 | const commits = await getCommits(); 83 | 84 | res.json({ 85 | ok: true, 86 | commits, 87 | whatsNew 88 | }); 89 | }); 90 | 91 | return router; 92 | }; 93 | -------------------------------------------------------------------------------- /src/webserver/routes/work.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Router } from "express"; 23 | import { ErrorBlockNotFound } from "../../errors/index.js"; 24 | import { getLastBlock } from "../../krist/blocks/index.js"; 25 | import { getDetailedUnpaid, getUnpaidNameCount } from "../../krist/names/index.js"; 26 | import { getWork, getWorkOverTime } from "../../krist/work.js"; 27 | import { getBaseBlockValue } from "../../utils/index.js"; 28 | 29 | export default (): Router => { 30 | const router = Router(); 31 | 32 | // =========================================================================== 33 | // API v2 34 | // =========================================================================== 35 | router.get("/work", async (req, res) => { 36 | res.json({ 37 | ok: true, 38 | work: await getWork() 39 | }); 40 | }); 41 | 42 | router.get("/work/day", async (req, res) => { 43 | res.json({ 44 | ok: true, 45 | work: await getWorkOverTime() 46 | }); 47 | }); 48 | 49 | router.get("/work/detailed", async (req, res) => { 50 | const lastBlock = await getLastBlock(); 51 | if (!lastBlock) throw new ErrorBlockNotFound(); 52 | 53 | const unpaidNames = await getUnpaidNameCount(); 54 | const baseValue = getBaseBlockValue(lastBlock.id); 55 | 56 | const detailedUnpaid = await getDetailedUnpaid(); 57 | const nextUnpaid = detailedUnpaid.find(u => u.unpaid > 0); 58 | const mostUnpaid = [...(detailedUnpaid.filter(u => u.unpaid > 0))]; 59 | mostUnpaid.sort((a, b) => b.unpaid - a.unpaid); 60 | 61 | res.json({ 62 | ok: true, 63 | 64 | work: await getWork(), 65 | unpaid: unpaidNames, 66 | 67 | base_value: baseValue, 68 | block_value: baseValue + unpaidNames, 69 | 70 | decrease: { 71 | value: nextUnpaid ? nextUnpaid.count : 0, 72 | blocks: nextUnpaid ? nextUnpaid.unpaid : 0, 73 | reset: mostUnpaid && mostUnpaid.length > 0 ? mostUnpaid[0].unpaid : 0 74 | } 75 | }); 76 | }); 77 | 78 | // =========================================================================== 79 | // Legacy API 80 | // =========================================================================== 81 | router.get("/", async (req, res, next) => { 82 | if (req.query.getwork !== undefined) { 83 | return res.send((await getWork()).toString()); 84 | } 85 | 86 | next(); 87 | }); 88 | 89 | return router; 90 | }; 91 | -------------------------------------------------------------------------------- /src/webserver/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { Request, Response } from "express"; 23 | import { PaginatedResult } from "../database/index.js"; 24 | 25 | export type ReqQuery = Request; 26 | export type PaginatedQuery = ReqQuery<{ 27 | limit?: string; 28 | offset?: string; 29 | } & T>; 30 | 31 | export function returnPaginatedResult( 32 | res: Response, 33 | name: string, 34 | mapper?: (row: RowT) => any, 35 | data?: PaginatedResult, 36 | extra?: ExtraT 37 | ): void { 38 | res.json({ 39 | ok: true, 40 | count: data?.rows.length ?? 0, 41 | total: data?.count ?? 0, 42 | [name]: (mapper ? data?.rows.map(mapper) : data?.rows) ?? [], 43 | ...extra 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /src/websockets/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { WebSocketsManager } from "./WebSocketManager.js"; 23 | 24 | export const wsManager = new WebSocketsManager(); 25 | -------------------------------------------------------------------------------- /src/websockets/prometheus.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import promClient from "prom-client"; 23 | import { wsManager } from "./index.js"; 24 | 25 | // ============================================================================= 26 | // COUNTERS 27 | // ============================================================================= 28 | export const promWebsocketConnectionsTotal = new promClient.Counter({ 29 | name: "krist_websocket_connections_total", 30 | help: "Total number of new websocket connections since the Krist server started.", 31 | labelNames: ["type"] 32 | }); 33 | promWebsocketConnectionsTotal.inc({ type: "incomplete" }, 0); 34 | promWebsocketConnectionsTotal.inc({ type: "guest" }, 0); 35 | promWebsocketConnectionsTotal.inc({ type: "authed" }, 0); 36 | 37 | export const promWebsocketTokensTotal = new promClient.Counter({ 38 | name: "krist_websocket_tokens_total", 39 | help: "Total number of websocket tokens created since the Krist server started.", 40 | labelNames: ["type"] 41 | }); 42 | promWebsocketTokensTotal.inc({ type: "guest" }, 0); 43 | promWebsocketTokensTotal.inc({ type: "authed" }, 0); 44 | 45 | export const promWebsocketMessagesTotal = new promClient.Counter({ 46 | name: "krist_websocket_incoming_messages_total", 47 | help: "Total number of incoming websocket messages since the Krist server started.", 48 | labelNames: ["type"] 49 | }); 50 | 51 | export const promWebsocketEventBroadcastsTotal = new promClient.Counter({ 52 | name: "krist_websocket_event_broadcasts_total", 53 | help: "Total number of websocket event broadcasts sent out since the Krist server started.", 54 | labelNames: ["event"] 55 | }); 56 | 57 | export const promWebsocketKeepalivesTotal = new promClient.Counter({ 58 | name: "krist_websocket_keepalives_total", 59 | help: "Total number of websocket keepalives sent out since the Krist server started.", 60 | labelNames: ["type"] 61 | }); 62 | promWebsocketTokensTotal.inc({ type: "guest" }, 0); 63 | promWebsocketTokensTotal.inc({ type: "authed" }, 0); 64 | 65 | // ============================================================================= 66 | // GAUGES 67 | // ============================================================================= 68 | new promClient.Gauge({ 69 | name: "krist_websocket_connections_current", 70 | help: "Current number of active websocket connections.", 71 | labelNames: ["type"], 72 | collect() { 73 | if (!wsManager) throw new Error("No wsManager!!"); 74 | const s = wsManager.sockets; 75 | this.set({ type: "guest" }, s.filter(w => w.isGuest).length); 76 | this.set({ type: "authed" }, s.filter(w => !w.isGuest).length); 77 | } 78 | }); 79 | 80 | new promClient.Gauge({ 81 | name: "krist_websocket_tokens_pending_current", 82 | help: "Current number of pending websocket tokens.", 83 | collect() { 84 | this.set(Object.keys(wsManager.pendingTokens).length); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /src/websockets/routes/addresses.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { ctrlGetAddress } from "../../controllers/addresses.js"; 23 | import { addressToJson } from "../../krist/addresses/index.js"; 24 | import { WebSocketEventHandler } from "../types.js"; 25 | 26 | /** 27 | * @api {ws} /type/address Get an address 28 | * @apiName WSGetAddress 29 | * @apiGroup WebsocketGroup 30 | * @apiVersion 2.0.4 31 | * 32 | * @apiBody {Number} id 33 | * @apiBody {String="address"} type 34 | * @apiBody {String} address 35 | * @apiBody {Boolean} [fetchNames] When supplied, fetch 36 | * the count of names owned by the address. 37 | * 38 | * @apiUse Address 39 | */ 40 | export const wsGetAddress: WebSocketEventHandler<{ 41 | address?: string; 42 | fetchNames?: boolean; 43 | }> = async (_, msg) => { 44 | const fetchNames = !!msg.fetchNames; 45 | const address = await ctrlGetAddress(msg.address, fetchNames); 46 | 47 | return { 48 | ok: true, 49 | address: addressToJson(address) 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /src/websockets/routes/index.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { ErrorInvalidParameter } from "../../errors/index.js"; 23 | import { WebSocketEventHandler, WebSocketMessageType } from "../types.js"; 24 | import { wsGetAddress } from "./addresses.js"; 25 | import { wsLogin } from "./login.js"; 26 | import { wsLogout } from "./logout.js"; 27 | import { wsGetMe } from "./me.js"; 28 | import { wsSubmitBlock } from "./submission.js"; 29 | import { wsGetSubscriptionLevel, wsGetValidSubscriptionLevels, wsSubscribe, wsUnsubscribe } from "./subscription.js"; 30 | import { wsMakeTransaction } from "./transactions.js"; 31 | import { wsGetWork } from "./work.js"; 32 | 33 | export const WEBSOCKET_HANDLERS: Record> = { 34 | "address": wsGetAddress, 35 | "make_transaction": wsMakeTransaction, 36 | "me": wsGetMe, 37 | "submit_block": wsSubmitBlock, 38 | "work": wsGetWork, 39 | 40 | "subscribe": wsSubscribe, 41 | "unsubscribe": wsUnsubscribe, 42 | "get_subscription_level": wsGetSubscriptionLevel, 43 | "get_valid_subscription_levels": wsGetValidSubscriptionLevels, 44 | 45 | "login": wsLogin, 46 | "logout": wsLogout 47 | }; 48 | 49 | export const handleWebSocketMessage: WebSocketEventHandler = async (ws, msg) => { 50 | const handler = WEBSOCKET_HANDLERS[msg.type]; 51 | if (handler) return handler(ws, msg); 52 | else throw new ErrorInvalidParameter("type"); 53 | }; 54 | -------------------------------------------------------------------------------- /src/websockets/routes/login.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { ErrorMissingParameter } from "../../errors/index.js"; 24 | import { addressToJson } from "../../krist/addresses/index.js"; 25 | import { verifyAddress } from "../../krist/addresses/verify.js"; 26 | import { getLogDetails } from "../../utils/index.js"; 27 | import { WebSocketEventHandler } from "../types.js"; 28 | 29 | /** 30 | * @api {ws} /type/login Login to a wallet (upgrade connection) 31 | * @apiName WSLogin 32 | * @apiGroup WebsocketGroup 33 | * @apiVersion 2.0.3 34 | * 35 | * @apiBody {Number} id 36 | * @apiBody {String="login"} type 37 | * @apiBody {String} privatekey 38 | * 39 | * @apiSuccess {Boolean} isGuest Whether the current user is a guest or not 40 | * @apiUse Address 41 | */ 42 | export const wsLogin: WebSocketEventHandler<{ 43 | privatekey?: string; 44 | }> = async (ws, { privatekey }) => { 45 | if (!privatekey) throw new ErrorMissingParameter("privatekey"); 46 | 47 | const { logDetails } = getLogDetails(ws.req); 48 | const { address, authed } = await verifyAddress(ws.req, privatekey); 49 | 50 | if (authed) { 51 | console.log(chalkT`{cyan [Websockets]} Session {bold ${ws.address}} logging in as {bold ${address.address}} ${logDetails}`); 52 | 53 | ws.address = address.address; 54 | ws.privatekey = privatekey; 55 | ws.isGuest = false; 56 | 57 | return { 58 | ok: true, 59 | isGuest: false, 60 | address: addressToJson(address) 61 | }; 62 | } else { 63 | console.log(chalkT`{red [Websockets]} Session {bold ${ws.address}} failed login as {bold ${address.address}} ${logDetails}`); 64 | 65 | ws.address = "guest"; 66 | ws.isGuest = true; 67 | 68 | return { 69 | ok: true, 70 | isGuest: true 71 | }; 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/websockets/routes/logout.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { getLogDetails } from "../../utils/index.js"; 24 | import { WebSocketEventHandler } from "../types.js"; 25 | 26 | /** 27 | * @api {ws} /type/logout Log out back to guest (downgrade connection) 28 | * @apiName WSLogout 29 | * @apiGroup WebsocketGroup 30 | * @apiVersion 2.0.3 31 | * 32 | * @apiBody {Number} id 33 | * @apiBody {String="logout"} type 34 | * 35 | * @apiSuccess {Boolean} isGuest Whether the current user is a guest or not 36 | */ 37 | export const wsLogout: WebSocketEventHandler = async ws => { 38 | const { logDetails } = getLogDetails(ws.req); 39 | console.log(chalkT`{cyan [Websockets]} Session {bold ${ws.address}} logged out ${logDetails}`); 40 | 41 | ws.address = "guest"; 42 | ws.isGuest = true; 43 | 44 | return { 45 | ok: true, 46 | isGuest: true 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/websockets/routes/me.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { ErrorAddressNotFound } from "../../errors/index.js"; 23 | import { addressToJson, getAddress } from "../../krist/addresses/index.js"; 24 | import { WebSocketEventHandler } from "../types.js"; 25 | 26 | /** 27 | * @api {ws} /type/me Get information about the user 28 | * @apiName WSMe 29 | * @apiGroup WebsocketGroup 30 | * @apiVersion 2.0.2 31 | * 32 | * @apiBody {Number} id 33 | * @apiBody {String="me"} type 34 | * 35 | * @apiSuccess {Boolean} isGuest Whether the current user is a guest or not 36 | * @apiUse Address 37 | * 38 | * @apiSuccessExample {json} Success as guest 39 | * { 40 | * "ok": true, 41 | * "id": 1, 42 | * "isGuest": true 43 | * } 44 | * 45 | * @apiSuccessExample {json} Success as authed user 46 | * { 47 | * "ok": true, 48 | * "id": 1, 49 | * "isGuest": false, 50 | * "address": { 51 | * "address": "knggsn1d2e", 52 | * "balance": 0, 53 | * "totalin": 0, 54 | * "totalout": 0, 55 | * "firstseen": "2016-06-17T21:09:28.000Z" 56 | * } 57 | * } 58 | */ 59 | export const wsGetMe: WebSocketEventHandler = async ws => { 60 | if (ws.isGuest) { 61 | return { ok: true, isGuest: true }; 62 | } else { 63 | const address = await getAddress(ws.address); 64 | if (!address) throw new ErrorAddressNotFound(ws.address); 65 | return { ok: true, isGuest: false, address: addressToJson(address) }; 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/websockets/routes/submission.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { ctrlSubmitBlock } from "../../controllers/blocks.js"; 23 | import { ErrorMissingParameter, ErrorSolutionDuplicate, ErrorSolutionIncorrect } from "../../errors/index.js"; 24 | import { addressToJson } from "../../krist/addresses/index.js"; 25 | import { blockToJson } from "../../krist/blocks/index.js"; 26 | import { WebSocketEventHandler } from "../types.js"; 27 | 28 | export const wsSubmitBlock: WebSocketEventHandler<{ 29 | address?: string; 30 | nonce?: number[] | string; 31 | }> = async (ws, { address, nonce }) => { 32 | try { 33 | if (ws.isGuest && !address) 34 | throw new ErrorMissingParameter("address"); 35 | 36 | const result = await ctrlSubmitBlock(ws.req, 37 | address || ws.address, nonce); 38 | 39 | return { 40 | ok: true, 41 | success: true, 42 | work: result.work, 43 | address: addressToJson(result.address), 44 | block: blockToJson(result.block) 45 | }; 46 | } catch (err: unknown) { 47 | if (err instanceof ErrorSolutionIncorrect 48 | || err instanceof ErrorSolutionDuplicate) { 49 | return { 50 | ok: true, 51 | success: false, 52 | error: err.errorString || "unknown_error" 53 | }; 54 | } else { 55 | throw err; // let the websocket handle the original error 56 | } 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/websockets/routes/transactions.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { ctrlMakeTransaction } from "../../controllers/transactions.js"; 23 | import { ErrorMissingParameter } from "../../errors/index.js"; 24 | import { transactionToJson } from "../../krist/transactions/index.js"; 25 | import { WebSocketEventHandler } from "../types.js"; 26 | 27 | /** 28 | * @api {ws} /type/make_transaction Make a transaction 29 | * @apiName WSMakeTransaction 30 | * @apiGroup WebsocketGroup 31 | * @apiVersion 2.0.7 32 | * 33 | * @apiBody {Number} id 34 | * @apiBody {String="make_transaction"} type 35 | * @apiBody {String} [privatekey] The privatekey of your address. 36 | * @apiBody {String} to The recipient of the transaction. 37 | * @apiBody {Number} amount The amount to send to the recipient. 38 | * @apiBody {String} [metadata] Optional metadata to include in the transaction. 39 | * @apiBody {String} [requestId] Optional request ID to use for this transaction. Must be a valid UUIDv4 if provided. 40 | * 41 | * @apiUse Transaction 42 | * 43 | * @apiSuccessExample {json} Success 44 | * { 45 | * "ok": true 46 | * } 47 | * 48 | * @apiErrorExample {json} Insufficient Funds 49 | * { 50 | * "ok": false, 51 | * "error": "insufficient_funds" 52 | * } 53 | * 54 | * @apiErrorExample {json} Transaction Conflict 55 | * { 56 | * "ok": false, 57 | * "error": "transaction_conflict", 58 | * "parameter": "amount" 59 | * } 60 | */ 61 | export const wsMakeTransaction: WebSocketEventHandler<{ 62 | privatekey?: string; 63 | to?: string; 64 | amount?: string | number; 65 | metadata?: string; 66 | requestId?: string; 67 | }> = async (ws, msg) => { 68 | if (ws.isGuest && !msg.privatekey) 69 | throw new ErrorMissingParameter("privatekey"); 70 | 71 | const tx = await ctrlMakeTransaction( 72 | ws.req, 73 | msg.privatekey || ws.privatekey, 74 | msg.to, 75 | msg.amount, 76 | msg.metadata, 77 | msg.requestId 78 | ); 79 | 80 | return { 81 | ok: true, 82 | transaction: transactionToJson(tx) 83 | }; 84 | }; 85 | -------------------------------------------------------------------------------- /src/websockets/routes/work.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { getWork } from "../../krist/work.js"; 23 | import { WebSocketEventHandler } from "../types.js"; 24 | 25 | /** 26 | * @api {ws} /type/work Get the current work 27 | * @apiName WSGetWork 28 | * @apiGroup WebsocketGroup 29 | * @apiVersion 2.0.1 30 | * 31 | * @apiBody {Number} id 32 | * @apiBody {String="work"} type 33 | * 34 | * @apiSuccess {Number} work The current Krist work (difficulty) 35 | * 36 | * @apiSuccessExample {json} Success 37 | * { 38 | * "ok": true, 39 | * "id": 1, 40 | * "work": 18750 41 | * } 42 | */ 43 | export const wsGetWork: WebSocketEventHandler = async () => { 44 | return { 45 | ok: true, 46 | work: await getWork() 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/websockets/subscriptionCheck.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { WebSocketBlockEvent, WebSocketEventMessage, WebSocketNameEvent, WebSocketTransactionEvent } from "./types.js"; 23 | import { WrappedWebSocket } from "./WrappedWebSocket.js"; 24 | 25 | /** Returns a function, based on the event type, that checks whether a given websocket should receive the event. */ 26 | export function subscriptionCheck( 27 | msg: WebSocketEventMessage 28 | ): (ws: WrappedWebSocket) => boolean { 29 | if (!msg.event) throw new Error("Missing event type"); 30 | 31 | switch (msg.event) { 32 | // If the ws is subscribed to 'blocks' or 'ownBlocks' 33 | case "block": { 34 | const { address } = (msg as WebSocketBlockEvent).block; 35 | return ws => 36 | (!ws.isGuest && ws.address === address && ws.subs.includes("ownBlocks")) 37 | || ws.subs.includes("blocks"); 38 | } 39 | 40 | // If the ws is subscribed to 'transactions' or 'ownTransactions' 41 | case "transaction": { 42 | const { to, from } = (msg as WebSocketTransactionEvent).transaction; 43 | return ws => 44 | ( 45 | !ws.isGuest 46 | && (ws.address === to || ws.address === from) 47 | && ws.subs.includes("ownTransactions") 48 | ) 49 | || ws.subs.includes("transactions"); 50 | } 51 | 52 | // If the ws is subscribed to 'names' or 'ownNames' 53 | case "name": { 54 | const { owner } = (msg as WebSocketNameEvent).name; 55 | return ws => 56 | (!ws.isGuest && (ws.address === owner) && ws.subs.includes("ownNames")) 57 | || ws.subs.includes("names"); 58 | } 59 | 60 | default: 61 | throw new Error("Unknown event type " + msg.event); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/websockets/types.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { BlockJson } from "../krist/blocks/index.js"; 23 | import { NameJson } from "../krist/names/index.js"; 24 | import { TransactionJson } from "../krist/transactions/index.js"; 25 | import { lut } from "../utils/index.js"; 26 | import { WrappedWebSocket } from "./WrappedWebSocket.js"; 27 | 28 | export type WebSocketSubscription = "blocks" | "ownBlocks" | "transactions" | 29 | "ownTransactions" | "names" | "ownNames" | "motd"; 30 | export const SUBSCRIPTIONS = ["blocks", "ownBlocks", "transactions", 31 | "ownTransactions", "names", "ownNames", "motd"]; 32 | export const VALID_SUBSCRIPTIONS = lut(SUBSCRIPTIONS); 33 | 34 | export type WebSocketMessageType = "address" | "login" | "logout" | "me" | 35 | "submit_block" | "subscribe" | "get_subscription_level" | 36 | "get_valid_subscription_levels" | "unsubscribe" | "make_transaction" | "work"; 37 | export const MESSAGE_TYPES = ["address", "login", "logout", "me", 38 | "submit_block", "subscribe", "get_subscription_level", 39 | "get_valid_subscription_levels", "unsubscribe", "make_transaction", "work"]; 40 | export const VALID_MESSAGE_TYPES = lut(MESSAGE_TYPES); 41 | 42 | export type WebSocketEventType = "block" | "transaction" | "name"; 43 | 44 | export type WebSocketEventHandler = ( 45 | ws: WrappedWebSocket, 46 | msg: IncomingWebSocketMessage & T 47 | ) => Promise; 48 | 49 | export interface WebSocketTokenData { 50 | address: string; 51 | privatekey?: string; 52 | } 53 | 54 | export interface IncomingWebSocketMessage { 55 | id: string | number; 56 | type: WebSocketMessageType; 57 | } 58 | 59 | export interface OutgoingWebSocketMessage { 60 | ok: boolean; 61 | } 62 | 63 | export interface WebSocketEventMessage extends Omit { 64 | type: "event"; 65 | event: WebSocketEventType; 66 | } 67 | 68 | export interface WebSocketBlockEvent extends WebSocketEventMessage { 69 | event: "block"; 70 | block: BlockJson; 71 | new_work: number; 72 | } 73 | 74 | export interface WebSocketTransactionEvent extends WebSocketEventMessage { 75 | event: "transaction"; 76 | transaction: TransactionJson; 77 | } 78 | 79 | export interface WebSocketNameEvent extends WebSocketEventMessage { 80 | event: "name"; 81 | name: NameJson; 82 | } 83 | -------------------------------------------------------------------------------- /static/down.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Krist 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 31 | 32 | 33 |
34 |

Krist

35 |

A minable currency that works across servers

36 | 37 |

Created by 3d6 and Lemmmy

38 |
39 |
40 |
41 |

Server down

42 |

The Krist server is currently down. It will be back shortly.

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /static/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/static/favicon-128x128.png -------------------------------------------------------------------------------- /static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/static/favicon-16x16.png -------------------------------------------------------------------------------- /static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/static/favicon-32x32.png -------------------------------------------------------------------------------- /static/favicon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/static/favicon-64x64.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmpim/Krist/9670f71b382f68a255668fedca10ae154e6767b9/static/favicon.ico -------------------------------------------------------------------------------- /static/logo2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chai from "chai"; 23 | 24 | export const api = (): ChaiHttp.Agent => 25 | chai.request("http://localhost:8080"); 26 | -------------------------------------------------------------------------------- /test/fixtures.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import "dotenv/config"; 23 | import * as chai from "chai"; 24 | import chaiHttp from "chai-http"; 25 | import chalkT from "chalk-template"; 26 | import { RootHookObject } from "mocha"; 27 | import { register } from "prom-client"; 28 | import sinon, { SinonSandbox } from "sinon"; 29 | import { db, initDatabase } from "../src/database/index.js"; 30 | import { initRedis, redis, rKey } from "../src/database/redis.js"; 31 | import { initKrist } from "../src/krist/index.js"; 32 | import { shutdownWorkOverTime } from "../src/krist/work.js"; 33 | import { initWebserver, server } from "../src/webserver/index.js"; 34 | 35 | register.clear(); 36 | 37 | export async function mochaGlobalSetup(): Promise { 38 | console.log(chalkT`{red [Tests]} Global setup`); 39 | 40 | chai.use(chaiHttp); 41 | chai.config.truncateThreshold = 0; 42 | 43 | await initRedis(); 44 | await initDatabase(true); 45 | await db.sync({ force: true }); 46 | await initWebserver(); 47 | await initKrist(); 48 | } 49 | 50 | export async function mochaGlobalTeardown(): Promise { 51 | console.log(chalkT`{red [Tests]} Stopping web server and database`); 52 | 53 | // Undo some changes made by the test runner 54 | await redis.set(rKey("mining-enabled"), "false"); 55 | 56 | server.close(); 57 | await db.close(); 58 | shutdownWorkOverTime(); 59 | } 60 | 61 | let sandbox: SinonSandbox; 62 | export function mochaHooks(): RootHookObject { 63 | return { 64 | beforeEach(done) { 65 | // Suppress Krist's rather verbose logging during tests 66 | if (!process.env.TEST_DEBUG) { 67 | sandbox = sinon.createSandbox(); 68 | sandbox.stub(console, "log"); 69 | } 70 | done(); 71 | }, 72 | 73 | afterEach(done) { 74 | if (!process.env.TEST_DEBUG) sandbox.restore(); 75 | done(); 76 | } 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /test/misc/json.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | 24 | import { Transaction } from "../../src/database/index.js"; 25 | import { transactionToJson } from "../../src/krist/transactions/index.js"; 26 | 27 | import { seed } from "../seed.js"; 28 | 29 | describe("schema to json", function() { 30 | before(seed); 31 | 32 | describe("transactionToJSON", function() { 33 | it("should convert a database transaction to json", async function() { 34 | const time = new Date(); 35 | const tx = await Transaction.create({ 36 | id: 1, from: "k8juvewcui", to: "k7oax47quv", value: 1, 37 | time, name: null, op: null 38 | }); 39 | 40 | const out = transactionToJson(tx); 41 | 42 | expect(out).to.be.an("object"); 43 | expect(out).to.deep.include({ 44 | id: 1, from: "k8juvewcui", to: "k7oax47quv", value: 1, 45 | time: time.toISOString(), type: "transfer" 46 | }); 47 | }); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/misc/krist.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | import { isValidKristAddress, isValidName, stripNameSuffix } from "../../src/utils/index.js"; 24 | 25 | describe("krist functions", function() { 26 | describe("isValidKristAddress", function() { 27 | it("should work for a valid v1 address", async function() { return expect(isValidKristAddress("a5dfb396d3")).to.be.true; }); 28 | 29 | it("should work for a valid v2 address", async function() { return expect(isValidKristAddress("k8juvewcui")).to.be.true; }); 30 | 31 | it("should fail for an invalid address", async function() { return expect(isValidKristAddress("kfartoolong")).to.be.false; }); 32 | 33 | it("should fail for a valid v1 address when v2Only", async function() { return expect(isValidKristAddress("a5dfb396d3", true)).to.be.false; }); 34 | 35 | it("should work for a valid v2 address when v2Only", async function() { return expect(isValidKristAddress("k8juvewcui", true)).to.be.true; }); 36 | 37 | it("should fail for an invalid address when v2Only", async function() { return expect(isValidKristAddress("kfartoolong", true)).to.be.false; }); 38 | }); 39 | 40 | describe("isValidName", function() { 41 | it("should work for a valid name", async function() { return expect(isValidName("test")).to.be.true; }); 42 | 43 | it("should not allow symbols", async function() { return expect(isValidName("test[")).to.be.false; }); 44 | 45 | it("should not allow spaces", async function() { return expect(isValidName("te st")).to.be.false; }); 46 | 47 | it("should not trim", async function() { return expect(isValidName(" test ")).to.be.false; }); 48 | 49 | it("should not allow empty names", async function() { return expect(isValidName("")).to.be.false; }); 50 | 51 | it("should not allow long names", async function() { return expect(isValidName("a".repeat(65))).to.be.false; }); 52 | 53 | it("should error with undefined", async function() { return expect(() => (isValidName as any)()).to.throw(TypeError); }); 54 | 55 | it("should not allow punycode prefixes", async function() { return expect(isValidName("xn--test")).to.be.false; }); 56 | 57 | it("should allow punycode prefixes with fetching=true", async function() { return expect(isValidName("xn--test", true)).to.be.true; }); 58 | }); 59 | 60 | describe("stripNameSuffix", function() { 61 | it("should strip a .kst suffix", async function() { return expect(stripNameSuffix("test.kst")).to.equal("test"); }); 62 | 63 | it("not alter a name without a suffix", async function() { return expect(stripNameSuffix("test")).to.equal("test"); }); 64 | 65 | it("should only strip the last suffix", async function() { return expect(stripNameSuffix("test.kst.kst")).to.equal("test.kst"); }); 66 | 67 | it("should not error with an undefined input", async function() { return expect((stripNameSuffix as any)()).to.equal(""); }); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/routes/blocks.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { seed } from "../seed.js"; 23 | 24 | describe("v1 routes: blocks", function() { 25 | before(seed); 26 | 27 | // TODO: /?lastblock 28 | // TODO: /?getbaseblockvalue 29 | // TODO: /?getblockvalue 30 | // TODO: /?blocks 31 | }); 32 | 33 | describe("v2 routes: blocks", function() { 34 | before(seed); 35 | 36 | // TODO: GET /blocks 37 | // TODO: GET /blocks/latest 38 | // TODO: GET /blocks/lowest 39 | // TODO: GET /blocks/last 40 | // TODO: GET /blocks/basevalue 41 | // TODO: GET /blocks/value 42 | // TODO: GET /blocks/:height 43 | }); 44 | -------------------------------------------------------------------------------- /test/routes/login.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | import { Address } from "../../src/database/index.js"; 24 | import { api } from "../api.js"; 25 | import { seed } from "../seed.js"; 26 | 27 | describe("v2 routes: login", function() { 28 | before(seed); 29 | 30 | describe("POST /login", function() { 31 | it("should error with a missing privatekey", async function() { 32 | const res = await api().post("/login"); 33 | expect(res).to.be.json; 34 | expect(res.body).to.deep.include({ ok: false, error: "missing_parameter", parameter: "privatekey" }); 35 | }); 36 | 37 | it("should error with a blank privatekey", async function() { 38 | const res = await api().post("/login").send({ privatekey: "" }); 39 | expect(res).to.be.json; 40 | expect(res.body).to.deep.include({ ok: false, error: "missing_parameter", parameter: "privatekey" }); 41 | }); 42 | 43 | it("should error for v1", async function() { 44 | const res = await api().post("/login").query({ v: "1" }).send({ privatekey: "a" }); 45 | expect(res).to.be.json; 46 | expect(res.body).to.deep.include({ ok: false, error: "invalid_parameter", parameter: "v" }); 47 | }); 48 | 49 | it("should return with failed auth", async function() { 50 | const res = await api().post("/login").send({ privatekey: "c" }); 51 | expect(res).to.be.json; 52 | expect(res.body).to.deep.equal({ ok: true, authed: false }); 53 | }); 54 | 55 | it("should auth v2 addresses successfully", async function() { 56 | const res = await api().post("/login").send({ privatekey: "a" }); 57 | expect(res).to.be.json; 58 | expect(res.body).to.deep.include({ ok: true, authed: true, address: "k8juvewcui" }); 59 | }); 60 | 61 | it("should error for locked addresses even without a privatekey", async function() { 62 | const address = await Address.findOne({ where: { address: "kwsgj3x184" } }); 63 | if (!address) throw new Error("Address not found"); 64 | 65 | const oldPrivatekey = address.privatekey; 66 | address.privatekey = null; 67 | await address.save(); 68 | 69 | const res = await api().post("/login").send({ privatekey: "c" }); 70 | expect(res).to.be.json; 71 | expect(res.body).to.deep.include({ ok: true, authed: false }); 72 | 73 | address.privatekey = oldPrivatekey; 74 | await address.save(); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/routes/motd.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | import { api } from "../api.js"; 24 | import { seed } from "../seed.js"; 25 | 26 | describe("v2 routes: motd", function() { 27 | before(seed); 28 | 29 | it("should return the motd", async function() { 30 | const res = await api().get("/motd"); 31 | expect(res).to.have.status(200); 32 | expect(res).to.be.json; 33 | 34 | expect(res.body.ok).to.be.true; 35 | 36 | expect(res.body.motd).to.equal("Welcome to Krist!"); 37 | expect(res.body.set).to.be.ok; 38 | expect(res.body.motd_set).to.be.ok; 39 | 40 | expect(res.body.public_url).to.equal(process.env.PUBLIC_URL || "localhost:8080"); 41 | expect(res.body.mining_enabled).to.be.false; 42 | expect(res.body.transactions_enabled).to.be.true; 43 | expect(res.body.debug_mode).to.be.true; 44 | 45 | expect(res.body.work).to.equal(100000); 46 | expect(res.body.last_block).to.be.an("object"); 47 | expect(res.body.last_block.height).to.equal(1); 48 | 49 | expect(res.body.package).to.be.an("object"); 50 | expect(res.body.package).to.deep.include({ name: "krist", author: "Lemmmy", licence: "GPL-3.0" }); 51 | expect(res.body.package.version).to.be.ok; 52 | expect(res.body.package.repository).to.be.ok; 53 | 54 | expect(res.body.constants).to.be.an("object"); 55 | expect(res.body.constants).to.deep.include({ 56 | nonce_max_size: 24, name_cost: 500, min_work: 1, max_work: 100000, 57 | work_factor: 0.025, seconds_per_block: 300 58 | }); 59 | expect(res.body.constants.wallet_version).to.be.ok; 60 | 61 | expect(res.body.currency).to.be.an("object"); 62 | expect(res.body.currency).to.deep.equal({ 63 | address_prefix: "k", name_suffix: "kst", 64 | currency_name: "Krist", currency_symbol: "KST" 65 | }); 66 | 67 | expect(res.body.notice).to.equal("Krist was originally created by 3d6 and Lemmmy. It is now owned and operated by tmpim, and licensed under GPL-3.0."); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /test/routes/work.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | import { setWork } from "../../src/krist/work.js"; 24 | import { api } from "../api.js"; 25 | import { seed } from "../seed.js"; 26 | 27 | describe("v1 routes: work", function() { 28 | before(seed); 29 | 30 | describe("GET /?getwork", function() { 31 | it("should return the work", async function() { 32 | const res = await api().get("/?getwork"); 33 | expect(res).to.have.status(200); 34 | expect(res).to.be.text; 35 | expect(res.text).to.equal("100000"); 36 | }); 37 | }); 38 | }); 39 | 40 | describe("v2 routes: work", function() { 41 | before(seed); 42 | 43 | describe("GET /work", function() { 44 | it("should return the work", async function() { 45 | const res = await api().get("/work"); 46 | expect(res).to.have.status(200); 47 | expect(res).to.be.json; 48 | expect(res.body).to.deep.equal({ ok: true, work: 100000 }); 49 | }); 50 | 51 | it("should return the work when it changes", async function() { 52 | await setWork(5000); 53 | 54 | const res = await api().get("/work"); 55 | expect(res).to.have.status(200); 56 | expect(res).to.be.json; 57 | expect(res.body).to.deep.equal({ ok: true, work: 5000 }); 58 | }); 59 | }); 60 | 61 | describe("GET /work/day", function() { 62 | it("should return the work over time", async function() { 63 | const res = await api().get("/work/day"); 64 | expect(res).to.have.status(200); 65 | expect(res).to.be.json; 66 | expect(res.body.work).to.be.an("array"); 67 | }); 68 | }); 69 | 70 | describe("GET /work/detailed", function() { 71 | it("should return basic detailed work info", async function() { 72 | const res = await api().get("/work/detailed"); 73 | expect(res).to.have.status(200); 74 | expect(res).to.be.json; 75 | expect(res.body).to.be.deep.equal({ 76 | ok: true, 77 | work: 5000, unpaid: 0, 78 | base_value: 25, block_value: 25, 79 | decrease: { value: 0, blocks: 0, reset: 0 } 80 | }); 81 | }); 82 | 83 | // TODO: more tests here with names 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/seed.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import chalkT from "chalk-template"; 23 | import { Address, Block, db } from "../src/database/index.js"; 24 | import { redis, rKey } from "../src/database/redis.js"; 25 | import { REDIS_PREFIX, TEST_DEBUG } from "../src/utils/vars.js"; 26 | 27 | export async function seed(): Promise { 28 | const debug = TEST_DEBUG; 29 | 30 | // Cowardly refuse to wipe the databases if the database name is 'krist' (production username) 31 | const dbName = (db.options.replication.write as any).database; 32 | if (dbName === "krist") 33 | throw new Error("Refusing to wipe production databases in test runner. Check environment variables!!"); 34 | 35 | // Clear the databases 36 | if (debug) console.log(chalkT`{red [Tests]} Clearing the database {bold ${dbName}}`); 37 | await db.sync({ force: true }); 38 | 39 | if (debug) console.log(chalkT`{red [Tests]} Seeding the database {bold ${dbName}}`); 40 | await Promise.all([ 41 | // Create the genesis block 42 | Block.create({ 43 | value: 50, 44 | hash: "0000000000000000000000000000000000000000000000000000000000000000", 45 | address: "0000000000", 46 | nonce: "0", 47 | difficulty: 4294967295, 48 | time: new Date() 49 | }), 50 | 51 | // Create some addresses to test with 52 | Address.bulkCreate([ 53 | { address: "k8juvewcui", balance: 10, totalin: 0, totalout: 0, firstseen: new Date(), privatekey: "a350fa569fc53804c4282afbebafeba973c33238704815ea41fa8eec1f13a791" }, 54 | { address: "k7oax47quv", balance: 0, totalin: 0, totalout: 0, firstseen: new Date(), privatekey: "1f71334443b70c5c384894bc6308e9fcfb5b3103abb82eba6cd26d7767b5740c" }, 55 | { address: "kwsgj3x184", balance: 0, totalin: 0, totalout: 0, firstseen: new Date(), privatekey: "75185375f6e1e0eecbbe875355de2e38b7e548efbc80985479f5870967dcd2df", alert: "Test alert", locked: true }, 56 | { address: "k0duvsr4qn", balance: 25000, totalin: 0, totalout: 0, firstseen: new Date(), privatekey: "4827fb69dbc85b39204595dc870029d2a390a67b5275bd4588ae6567c01397d5" }, 57 | { address: "kvhccnbm95", balance: 100, totalin: 0, totalout: 0, firstseen: new Date(), privatekey: "c2b6cff7afc28c764ae239da2e785c1002f137dd9828416321f6c9a96b26f76e" }, 58 | ]) 59 | ]); 60 | 61 | // Reset the Redis database 62 | const redisKeys = await redis.keys(REDIS_PREFIX + "*"); 63 | 64 | if (debug) console.log(chalkT`{red [Tests]} Clearing {bold ${redisKeys.length}} redis keys with prefix {bold ${REDIS_PREFIX}}`); 65 | await Promise.all(redisKeys.map(key => redis.del(key))); 66 | 67 | await redis.set(rKey("work"), 100000); 68 | await redis.set(rKey("mining-enabled"), "true"); 69 | await redis.set(rKey("transactions-enabled"), "true"); 70 | } 71 | -------------------------------------------------------------------------------- /test/websocket_routes/addresses.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | import { seed } from "../seed.js"; 24 | import { newConnection } from "../ws.js"; 25 | 26 | describe("websocket routes: addresses", function() { 27 | before(seed); 28 | this.retries(4); 29 | 30 | describe("address", function() { 31 | it("should get an address", async function() { 32 | const ws = await newConnection(); 33 | expect(ws).to.nested.include({ "wsp.isOpened": true }); 34 | 35 | const data = await ws.sendAndWait({ type: "address", address: "k8juvewcui" }); 36 | expect(data).to.deep.include({ ok: true }); 37 | expect(data.address).to.be.an("object"); 38 | expect(data.address).to.include.all.keys("address", "balance", "totalin", "totalout", "firstseen"); 39 | expect(data.address).to.not.include.any.keys("id", "privatekey", "alert", "locked"); 40 | expect(data.address).to.deep.include({ address: "k8juvewcui", balance: 10 }); 41 | 42 | ws.close(); 43 | }); 44 | 45 | it("should get an address with names", async function() { 46 | const ws = await newConnection(); 47 | expect(ws).to.nested.include({ "wsp.isOpened": true }); 48 | 49 | const data = await ws.sendAndWait({ type: "address", address: "k8juvewcui", fetchNames: true }); 50 | expect(data).to.deep.include({ ok: true }); 51 | expect(data.address).to.be.an("object"); 52 | expect(data.address.names).to.equal(0); 53 | 54 | ws.close(); 55 | }); 56 | 57 | it("should error for missing addresses", async function() { 58 | const ws = await newConnection(); 59 | expect(ws).to.nested.include({ "wsp.isOpened": true }); 60 | 61 | const data = await ws.sendAndWait({ type: "address", address: "knotfound0" }); 62 | expect(data).to.deep.include({ ok: false, error: "address_not_found" }); 63 | 64 | ws.close(); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/websocket_routes/me.test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 - 2024 Drew Edwards, tmpim 3 | * 4 | * This file is part of Krist. 5 | * 6 | * Krist is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Krist is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Krist. If not, see . 18 | * 19 | * For more project information, see . 20 | */ 21 | 22 | import { expect } from "chai"; 23 | import { seed } from "../seed.js"; 24 | import { newConnection } from "../ws.js"; 25 | 26 | describe("websocket routes: me", function() { 27 | before(seed); 28 | this.retries(4); 29 | 30 | describe("me", function() { 31 | it("should work for guests", async function() { 32 | const ws = await newConnection(); 33 | expect(ws).to.nested.include({ "wsp.isOpened": true }); 34 | 35 | const data = await ws.sendAndWait({ type: "me" }); 36 | expect(data).to.deep.include({ ok: true, isGuest: true }); 37 | 38 | ws.close(); 39 | }); 40 | 41 | it("should work for authed users", async function() { 42 | const ws = await newConnection("a"); 43 | expect(ws).to.nested.include({ "wsp.isOpened": true }); 44 | 45 | const data = await ws.sendAndWait({ type: "me" }); 46 | expect(data).to.deep.include({ ok: true, isGuest: false }); 47 | expect(data.address).to.be.an("object"); 48 | expect(data.address).to.include.all.keys("address", "balance", "totalin", "totalout", "firstseen"); 49 | expect(data.address).to.not.include.any.keys("id", "privatekey", "alert", "locked"); 50 | expect(data.address).to.deep.include({ address: "k8juvewcui", balance: 10 }); 51 | 52 | ws.close(); 53 | }); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "allowJs": true, 5 | "target": "ES2022", 6 | "lib": [ 7 | "esnext" 8 | ], 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "downlevelIteration": true, 16 | "module": "NodeNext", 17 | "moduleResolution": "NodeNext", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noImplicitAny": true, 21 | "sourceMap": true, 22 | "experimentalDecorators": true, 23 | "emitDecoratorMetadata": true, 24 | "useDefineForClassFields": false, 25 | "typeRoots": ["./src/typings/", "./node_modules/@types"], 26 | }, 27 | "include": ["./src/**/*.ts", "./test/**/*.ts"], 28 | "exclude": ["./node_modules/", "./dist/"] 29 | } 30 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /views/error_404.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Not Found

3 |

The page you were looking for was not found.

4 |
5 | -------------------------------------------------------------------------------- /views/error_500.hbs: -------------------------------------------------------------------------------- 1 |
2 |

Error

3 |

There was an internal server error.

4 |
5 | -------------------------------------------------------------------------------- /views/home.hbs: -------------------------------------------------------------------------------- 1 |
2 | 7 | 8 |
9 |

Wallets

10 | 11 | 19 | 20 |

21 |

Disclaimer

22 |

23 | It should be noted that Krist does not use a block-chain, or any similar 24 | technology. Krist is not a "cryptocurrency". Krist is a centralized-database 25 | economy system for Minecraft servers. 26 |

27 | 28 |

29 |

Privacy

30 |

31 | The only PII that the Krist server stores is your IP address, User-Agent and 32 | Origin, as part of the webserver logs. This information is automatically 33 | purged after 30 days. 34 |

35 |
36 | 37 | 85 | -------------------------------------------------------------------------------- /views/layouts/main.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Krist 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 24 |

Krist

25 |
26 |

An economy system that works across servers

27 | 28 |

Created by 3d6 and Lemmmy

29 | {{#if debug}} 30 |

This is a development server running in debug mode.

31 | {{/if}} 32 |
33 |
34 | {{{body}}} 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /views/partials/link.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{> @partial-block }} 3 | 4 | -------------------------------------------------------------------------------- /views/partials/new.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{#if new}}New!{{/if}} 4 | {{> @partial-block }} 5 |
6 | 7 |
8 | {{#>link url=(concat "https://github.com/tmpim/Krist/commit/" hash)}} 9 | {{#if authorUsername}}{{/if}} 10 | {{#if authorName}} 11 | {{authorName}} 12 | 13 | {{/if}} 14 | 15 | {{date}} 16 | 17 | {{/link}} 18 |
19 |
20 | -------------------------------------------------------------------------------- /whats-new.json: -------------------------------------------------------------------------------- 1 | { 2 | "new": [{ 3 | "commitHash": "0882f0b1ea732b853c417ef7f1f78b312c45452a", 4 | "date": "2024-06-27T17:03:28+01:00", 5 | "new": true, 6 | "body": "Transaction and name creation endpoints now support a `requestId` parameter, to allow for idempotent transaction submission. [More info](https://github.com/tmpim/Krist/pull/62)" 7 | }, { 8 | "commitHash": "33d66ee55ef8f4eaa2b795faa34b244bc9727571", 9 | "date": "2023-03-22T21:30:30+00:00", 10 | "body": "The websocket gateway returned by `/ws/start` is now `wss://ws.krist.dev`. You should connect to the URL returned by `/ws/start` instead of hardcoding the URL." 11 | }, { 12 | "commitHash": "a7e1844f85ade36f5f7c8487713ed2e03caf0315", 13 | "date": "2022-07-17T19:37:00+01:00", 14 | "body": "The API URL has been changed to `krist.dev`." 15 | }, { 16 | "commitHash": "5fed5626e6ce0e24c6a4a3c574ce73d5a6cca69e", 17 | "date": "2022-04-07T23:24:01+01:00", 18 | "body": "Names have a new `transferred` field that returns when they were last transferred to a new owner." 19 | }, { 20 | "commitHash": "73097b66492728e9228123ae069dbc12aa7070f2", 21 | "date": "2021-03-01T11:46:43+00:00", 22 | "body": "Names now store their original owner in the `original_owner` field. Now you can check if they have been transferred by simply comparing these fields." 23 | }, { 24 | "commitHash": "9bad309b9ae0bc4e7e9b23496aff7b7ec7ea6bc5", 25 | "date": "2021-02-26T12:10:34+00:00", 26 | "body": "The [Search API](/docs/#api-LookupGroup-Search) has been added. It can be used to find addresses, transactions, names and blocks that match a given search query." 27 | }, { 28 | "commitHash": "af04c1c3a35ad3d1d0202f34387ed47e37ebdd25", 29 | "date": "2021-02-26T07:04:36+00:00", 30 | "body": "Transactions now return the name and metaname they were originally sent to, via the `sent_name` and `sent_metaname` fields. Now you no longer have to parse the metadata!" 31 | }, { 32 | "commitHash": "aaeb0a734157935c41a26847529f2a4b453509db", 33 | "date": "2021-02-24T08:25:25+00:00", 34 | "body": "[/motd](/motd) now returns a lot more info about the Krist node and its configuration. It also returns this for websockets when they first connect, via the `hello` message." 35 | }, { 36 | "commitHash": "4dd187f8038c43098f6c5b14f186393c043e3625", 37 | "date": "2021-02-20T02:29:30+00:00", 38 | "body": "A new endpoint ([/work/detailed](/work/detailed)) has been added to provide quick detailed information about when the block value will next decrease if a name has been recently purchased." 39 | }, { 40 | "commitHash": "83560438f13d16cff9d927d591763ec78db3239e", 41 | "date": "2020-12-23T02:39:13+00:00", 42 | "body": "Transactions now return their type (e.g. `mined`, `transfer`) in the API." 43 | }, { 44 | "commitHash": "38f0753f0322b13837ca7f093723d58d634dc501", 45 | "date": "2020-09-30T23:59:40-04:00", 46 | "authorUsername": "dmarcuse", 47 | "authorName": "Dana Marcuse", 48 | "body": "Block submission now supports binary nonces, up to 24 bytes long." 49 | }, { 50 | "commitHash": "../compare/79117d1303099607a20b0cd73be76f11b100a7e5...55e6b749dfdcde5a6ae5232c485719b60cd0bc2a", 51 | "date": "2020-09-15T18:48:23+01:00", 52 | "body": "The [Lookup API](/docs/#api-LookupGroup) has been added, allowing you to look up events in bulk (e.g. multiple addresses at once, transactions for multiple addresses)." 53 | }] 54 | } 55 | --------------------------------------------------------------------------------