🏡 index : ~doyle/blocks.ls.git

author Jordan Doyle <jordan@doyle.la> 2022-05-20 14:08:54.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2022-05-20 14:08:54.0 +00:00:00
commit
4cb31beb32713f4fb0d7450a44a094bc0c9cde93 [patch]
tree
b5de5d450318c8cf1f30676817f4c9619a0b4045
parent
802f8b7c8b1d8fc063b2f0f3cfef8a29c6429c27
download
4cb31beb32713f4fb0d7450a44a094bc0c9cde93.tar.gz

Call API for block info and parse script in frontend



Diff

 frontend/package-lock.json            | 536 +++++++++++++++++------------------
 frontend/package.json                 |   3 +-
 frontend/src/hooks.ts                 |   1 +-
 frontend/src/i18n.ts                  |   8 +-
 frontend/src/i18n/en.json             |  29 +--
 frontend/src/lib/bitcoinScript.ts     | 148 ++++++++++-
 frontend/src/lib/i18n.ts              |  16 +-
 frontend/src/lib/i18n/en.json         |  29 ++-
 frontend/src/routes/__layout.svelte   |  17 +-
 frontend/src/routes/block/[id].svelte | 100 ++++++-
 frontend/src/routes/index.svelte      |  44 +--
 frontend/svelte.config.js             |   1 +-
 package-lock.json                     |   6 +-
 web-api/src/database/blocks.rs        |  31 +-
 web-api/src/database/transactions.rs  |  12 +-
 web-api/src/methods/block.rs          |  48 ++-
 web-api/src/methods/mod.rs            |   1 +-
 17 files changed, 696 insertions(+), 334 deletions(-)

diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a19b047..11464ed 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,7 +8,8 @@
      "name": "list-blocks-frontend",
      "version": "0.0.1",
      "dependencies": {
        "svelte-i18n": "^3.4.0"
        "buffer": "^6.0.3",
        "sveltekit-i18n": "^2.2.1"
      },
      "devDependencies": {
        "@sveltejs/adapter-auto": "next",
@@ -51,50 +52,6 @@
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
      }
    },
    "node_modules/@formatjs/ecma402-abstract": {
      "version": "1.11.4",
      "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
      "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
      "dependencies": {
        "@formatjs/intl-localematcher": "0.2.25",
        "tslib": "^2.1.0"
      }
    },
    "node_modules/@formatjs/fast-memoize": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
      "integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
      "dependencies": {
        "tslib": "^2.1.0"
      }
    },
    "node_modules/@formatjs/icu-messageformat-parser": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
      "integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
      "dependencies": {
        "@formatjs/ecma402-abstract": "1.11.4",
        "@formatjs/icu-skeleton-parser": "1.3.6",
        "tslib": "^2.1.0"
      }
    },
    "node_modules/@formatjs/icu-skeleton-parser": {
      "version": "1.3.6",
      "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
      "integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
      "dependencies": {
        "@formatjs/ecma402-abstract": "1.11.4",
        "tslib": "^2.1.0"
      }
    },
    "node_modules/@formatjs/intl-localematcher": {
      "version": "0.2.25",
      "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
      "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
      "dependencies": {
        "tslib": "^2.1.0"
      }
    },
    "node_modules/@humanwhocodes/config-array": {
      "version": "0.9.5",
      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@@ -236,12 +193,12 @@
      }
    },
    "node_modules/@sveltejs/kit": {
      "version": "1.0.0-next.333",
      "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.333.tgz",
      "integrity": "sha512-gaj68NCVVKnzDPALzNbaP2xMaNvCZYxoreEGF6P8/bPAh4X6yB5OrgmtensoizPxSJW6d/Lz4LVw71LPmrQgkw==",
      "version": "1.0.0-next.336",
      "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.336.tgz",
      "integrity": "sha512-DfERqclfwI8Gdx8/p5qIU3vwEOQfWqC8+BXR12bNykbXFM1k8koDplRDpmGzcsXyhslf83j+RmchnkifbNLjsw==",
      "dev": true,
      "dependencies": {
        "@sveltejs/vite-plugin-svelte": "^1.0.0-next.32",
        "@sveltejs/vite-plugin-svelte": "^1.0.0-next.44",
        "chokidar": "^3.5.3",
        "sade": "^1.7.4",
        "vite": "^2.9.0"
@@ -283,6 +240,22 @@
        }
      }
    },
    "node_modules/@sveltekit-i18n/base": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/@sveltekit-i18n/base/-/base-1.2.1.tgz",
      "integrity": "sha512-F8gqG2+KAOeT0o2wYlUrW3TRCX7zaD7rBy/1CEVNw0irfw9TgFf/ODmhubkHHT3+6Zk+SMz8RNgeuffBfAMbJw==",
      "optionalDependencies": {
        "@sveltekit-i18n/parser-default": "^1.x"
      },
      "peerDependencies": {
        "svelte": "^3.x"
      }
    },
    "node_modules/@sveltekit-i18n/parser-default": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/@sveltekit-i18n/parser-default/-/parser-default-1.0.3.tgz",
      "integrity": "sha512-HheveklTjp3hxpYQhoHfyA6B4bQaUeSV5MQf2usIv/58UF2jY/YqhCAWj9bDBjufbuZc5pSz4BXvdX3WVT+viA=="
    },
    "node_modules/@types/json-schema": {
      "version": "7.0.11",
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -290,9 +263,9 @@
      "dev": true
    },
    "node_modules/@types/node": {
      "version": "17.0.34",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.34.tgz",
      "integrity": "sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==",
      "version": "17.0.35",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz",
      "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==",
      "dev": true
    },
    "node_modules/@types/pug": {
@@ -311,14 +284,14 @@
      }
    },
    "node_modules/@typescript-eslint/eslint-plugin": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.24.0.tgz",
      "integrity": "sha512-6bqFGk6wa9+6RrU++eLknKyDqXU1Oc8nyoLu5a1fU17PNRJd9UBr56rMF7c4DRaRtnarlkQ4jwxUbvBo8cNlpw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz",
      "integrity": "sha512-icYrFnUzvm+LhW0QeJNKkezBu6tJs9p/53dpPLFH8zoM9w1tfaKzVurkPotEpAqQ8Vf8uaFyL5jHd0Vs6Z0ZQg==",
      "dev": true,
      "dependencies": {
        "@typescript-eslint/scope-manager": "5.24.0",
        "@typescript-eslint/type-utils": "5.24.0",
        "@typescript-eslint/utils": "5.24.0",
        "@typescript-eslint/scope-manager": "5.25.0",
        "@typescript-eslint/type-utils": "5.25.0",
        "@typescript-eslint/utils": "5.25.0",
        "debug": "^4.3.4",
        "functional-red-black-tree": "^1.0.1",
        "ignore": "^5.2.0",
@@ -344,14 +317,14 @@
      }
    },
    "node_modules/@typescript-eslint/parser": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.24.0.tgz",
      "integrity": "sha512-4q29C6xFYZ5B2CXqSBBdcS0lPyfM9M09DoQLtHS5kf+WbpV8pBBhHDLNhXfgyVwFnhrhYzOu7xmg02DzxeF2Uw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.25.0.tgz",
      "integrity": "sha512-r3hwrOWYbNKP1nTcIw/aZoH+8bBnh/Lh1iDHoFpyG4DnCpvEdctrSl6LOo19fZbzypjQMHdajolxs6VpYoChgA==",
      "dev": true,
      "dependencies": {
        "@typescript-eslint/scope-manager": "5.24.0",
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/typescript-estree": "5.24.0",
        "@typescript-eslint/scope-manager": "5.25.0",
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/typescript-estree": "5.25.0",
        "debug": "^4.3.4"
      },
      "engines": {
@@ -371,13 +344,13 @@
      }
    },
    "node_modules/@typescript-eslint/scope-manager": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.24.0.tgz",
      "integrity": "sha512-WpMWipcDzGmMzdT7NtTjRXFabx10WleLUGrJpuJLGaxSqpcyq5ACpKSD5VE40h2nz3melQ91aP4Du7lh9FliCA==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz",
      "integrity": "sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==",
      "dev": true,
      "dependencies": {
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/visitor-keys": "5.24.0"
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/visitor-keys": "5.25.0"
      },
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -388,12 +361,12 @@
      }
    },
    "node_modules/@typescript-eslint/type-utils": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.24.0.tgz",
      "integrity": "sha512-uGi+sQiM6E5CeCZYBXiaIvIChBXru4LZ1tMoeKbh1Lze+8BO9syUG07594C4lvN2YPT4KVeIupOJkVI+9/DAmQ==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.25.0.tgz",
      "integrity": "sha512-B6nb3GK3Gv1Rsb2pqalebe/RyQoyG/WDy9yhj8EE0Ikds4Xa8RR28nHz+wlt4tMZk5bnAr0f3oC8TuDAd5CPrw==",
      "dev": true,
      "dependencies": {
        "@typescript-eslint/utils": "5.24.0",
        "@typescript-eslint/utils": "5.25.0",
        "debug": "^4.3.4",
        "tsutils": "^3.21.0"
      },
@@ -414,9 +387,9 @@
      }
    },
    "node_modules/@typescript-eslint/types": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.24.0.tgz",
      "integrity": "sha512-Tpg1c3shTDgTmZd3qdUyd+16r/pGmVaVEbLs+ufuWP0EruVbUiEOmpBBQxBb9a8iPRxi8Rb2oiwOxuZJzSq11A==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.25.0.tgz",
      "integrity": "sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==",
      "dev": true,
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -427,13 +400,13 @@
      }
    },
    "node_modules/@typescript-eslint/typescript-estree": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.24.0.tgz",
      "integrity": "sha512-zcor6vQkQmZAQfebSPVwUk/FD+CvnsnlfKXYeQDsWXRF+t7SBPmIfNia/wQxCSeu1h1JIjwV2i9f5/DdSp/uDw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz",
      "integrity": "sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==",
      "dev": true,
      "dependencies": {
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/visitor-keys": "5.24.0",
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/visitor-keys": "5.25.0",
        "debug": "^4.3.4",
        "globby": "^11.1.0",
        "is-glob": "^4.0.3",
@@ -454,15 +427,15 @@
      }
    },
    "node_modules/@typescript-eslint/utils": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.24.0.tgz",
      "integrity": "sha512-K05sbWoeCBJH8KXu6hetBJ+ukG0k2u2KlgD3bN+v+oBKm8adJqVHpSSLHNzqyuv0Lh4GVSAUgZ5lB4icmPmWLw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.25.0.tgz",
      "integrity": "sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==",
      "dev": true,
      "dependencies": {
        "@types/json-schema": "^7.0.9",
        "@typescript-eslint/scope-manager": "5.24.0",
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/typescript-estree": "5.24.0",
        "@typescript-eslint/scope-manager": "5.25.0",
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/typescript-estree": "5.25.0",
        "eslint-scope": "^5.1.1",
        "eslint-utils": "^3.0.0"
      },
@@ -478,12 +451,12 @@
      }
    },
    "node_modules/@typescript-eslint/visitor-keys": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.24.0.tgz",
      "integrity": "sha512-qzGwSXMyMnogcAo+/2fU+jhlPPVMXlIH2PeAonIKjJSoDKl1+lJVvG5Z5Oud36yU0TWK2cs1p/FaSN5J2OUFYA==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz",
      "integrity": "sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==",
      "dev": true,
      "dependencies": {
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/types": "5.25.0",
        "eslint-visitor-keys": "^3.3.0"
      },
      "engines": {
@@ -660,6 +633,25 @@
      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
      "dev": true
    },
    "node_modules/base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ]
    },
    "node_modules/binary-extensions": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -720,6 +712,29 @@
        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
      }
    },
    "node_modules/buffer": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ],
      "dependencies": {
        "base64-js": "^1.3.1",
        "ieee754": "^1.2.1"
      }
    },
    "node_modules/buffer-crc32": {
      "version": "0.2.13",
      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -883,6 +898,7 @@
      "version": "4.2.2",
      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
      "dev": true,
      "engines": {
        "node": ">=0.10.0"
      }
@@ -1568,7 +1584,8 @@
    "node_modules/estree-walker": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
      "dev": true
    },
    "node_modules/esutils": {
      "version": "2.0.3",
@@ -1760,7 +1777,8 @@
    "node_modules/globalyzer": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
      "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
      "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
      "dev": true
    },
    "node_modules/globby": {
      "version": "11.1.0",
@@ -1785,7 +1803,8 @@
    "node_modules/globrex": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
      "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
      "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
      "dev": true
    },
    "node_modules/graceful-fs": {
      "version": "4.2.10",
@@ -1814,6 +1833,25 @@
        "node": ">=8"
      }
    },
    "node_modules/ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/feross"
        },
        {
          "type": "patreon",
          "url": "https://www.patreon.com/feross"
        },
        {
          "type": "consulting",
          "url": "https://feross.org/support"
        }
      ]
    },
    "node_modules/ignore": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
@@ -1870,17 +1908,6 @@
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
      "dev": true
    },
    "node_modules/intl-messageformat": {
      "version": "9.13.0",
      "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
      "integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
      "dependencies": {
        "@formatjs/ecma402-abstract": "1.11.4",
        "@formatjs/fast-memoize": "1.2.1",
        "@formatjs/icu-messageformat-parser": "2.1.0",
        "tslib": "^2.1.0"
      }
    },
    "node_modules/is-binary-path": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -2091,6 +2118,7 @@
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
      "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
      "dev": true,
      "engines": {
        "node": ">=4"
      }
@@ -2251,9 +2279,9 @@
      }
    },
    "node_modules/postcss": {
      "version": "8.4.13",
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
      "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==",
      "version": "8.4.14",
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
      "dev": true,
      "funding": [
        {
@@ -2266,7 +2294,7 @@
        }
      ],
      "dependencies": {
        "nanoid": "^3.3.3",
        "nanoid": "^3.3.4",
        "picocolors": "^1.0.0",
        "source-map-js": "^1.0.2"
      },
@@ -2520,9 +2548,9 @@
      }
    },
    "node_modules/rollup": {
      "version": "2.73.0",
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.73.0.tgz",
      "integrity": "sha512-h/UngC3S4Zt28mB3g0+2YCMegT5yoftnQplwzPqGZcKvlld5e+kT/QRmJiL+qxGyZKOYpgirWGdLyEO1b0dpLQ==",
      "version": "2.74.1",
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.74.1.tgz",
      "integrity": "sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==",
      "dev": true,
      "bin": {
        "rollup": "dist/bin/rollup"
@@ -2561,6 +2589,7 @@
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
      "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
      "dev": true,
      "dependencies": {
        "mri": "^1.1.0"
      },
@@ -2593,9 +2622,9 @@
      }
    },
    "node_modules/sass": {
      "version": "1.51.0",
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.51.0.tgz",
      "integrity": "sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==",
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.52.0.tgz",
      "integrity": "sha512-6EnTglag2oVD8sNJCxUi2Jd3ICH9tJ5Mqudt/gIZNBR2uKJUBZuTpW9O1t04SkDLP7VFa76FCWTV2rwchqM8Kw==",
      "dev": true,
      "dependencies": {
        "chokidar": ">=3.0.0 <4.0.0",
@@ -2786,27 +2815,6 @@
        "svelte": ">=3.19.0"
      }
    },
    "node_modules/svelte-i18n": {
      "version": "3.4.0",
      "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-3.4.0.tgz",
      "integrity": "sha512-590N+YIRlebDT3fXmuAxd4guQZLR3vm4kCs5UhWYmw3SxOlJNZ7HwYYiw6d4jDr7P+Cx7DSopk1Z1K9wn8B6EA==",
      "dependencies": {
        "deepmerge": "^4.2.2",
        "estree-walker": "^2.0.1",
        "intl-messageformat": "^9.3.15",
        "sade": "^1.7.4",
        "tiny-glob": "^0.2.6"
      },
      "bin": {
        "svelte-i18n": "dist/cli.js"
      },
      "engines": {
        "node": ">= 11.15.0"
      },
      "peerDependencies": {
        "svelte": "^3.25.1"
      }
    },
    "node_modules/svelte-preprocess": {
      "version": "4.10.6",
      "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.6.tgz",
@@ -2882,6 +2890,18 @@
        "sourcemap-codec": "^1.4.8"
      }
    },
    "node_modules/sveltekit-i18n": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/sveltekit-i18n/-/sveltekit-i18n-2.2.1.tgz",
      "integrity": "sha512-1CyaRN6dBvp467JjBdji+nJf+7pZ3myFu+2YaCuGSAt09Cvt5ndfRbzy+aAd5WJdk6Lu/hnPEE7ZZFauTbDRNw==",
      "dependencies": {
        "@sveltekit-i18n/base": "1.2.1",
        "@sveltekit-i18n/parser-default": "1.0.3"
      },
      "peerDependencies": {
        "svelte": "^3.x"
      }
    },
    "node_modules/tailwindcss": {
      "version": "3.0.24",
      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.24.tgz",
@@ -2943,6 +2963,7 @@
      "version": "0.2.9",
      "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
      "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
      "dev": true,
      "dependencies": {
        "globalyzer": "0.1.0",
        "globrex": "^0.1.2"
@@ -2963,7 +2984,8 @@
    "node_modules/tslib": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
      "dev": true
    },
    "node_modules/tsutils": {
      "version": "3.21.0",
@@ -3167,50 +3189,6 @@
        "strip-json-comments": "^3.1.1"
      }
    },
    "@formatjs/ecma402-abstract": {
      "version": "1.11.4",
      "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz",
      "integrity": "sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw==",
      "requires": {
        "@formatjs/intl-localematcher": "0.2.25",
        "tslib": "^2.1.0"
      }
    },
    "@formatjs/fast-memoize": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz",
      "integrity": "sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg==",
      "requires": {
        "tslib": "^2.1.0"
      }
    },
    "@formatjs/icu-messageformat-parser": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz",
      "integrity": "sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw==",
      "requires": {
        "@formatjs/ecma402-abstract": "1.11.4",
        "@formatjs/icu-skeleton-parser": "1.3.6",
        "tslib": "^2.1.0"
      }
    },
    "@formatjs/icu-skeleton-parser": {
      "version": "1.3.6",
      "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz",
      "integrity": "sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg==",
      "requires": {
        "@formatjs/ecma402-abstract": "1.11.4",
        "tslib": "^2.1.0"
      }
    },
    "@formatjs/intl-localematcher": {
      "version": "0.2.25",
      "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz",
      "integrity": "sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA==",
      "requires": {
        "tslib": "^2.1.0"
      }
    },
    "@humanwhocodes/config-array": {
      "version": "0.9.5",
      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz",
@@ -3334,12 +3312,12 @@
      }
    },
    "@sveltejs/kit": {
      "version": "1.0.0-next.333",
      "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.333.tgz",
      "integrity": "sha512-gaj68NCVVKnzDPALzNbaP2xMaNvCZYxoreEGF6P8/bPAh4X6yB5OrgmtensoizPxSJW6d/Lz4LVw71LPmrQgkw==",
      "version": "1.0.0-next.336",
      "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-1.0.0-next.336.tgz",
      "integrity": "sha512-DfERqclfwI8Gdx8/p5qIU3vwEOQfWqC8+BXR12bNykbXFM1k8koDplRDpmGzcsXyhslf83j+RmchnkifbNLjsw==",
      "dev": true,
      "requires": {
        "@sveltejs/vite-plugin-svelte": "^1.0.0-next.32",
        "@sveltejs/vite-plugin-svelte": "^1.0.0-next.44",
        "chokidar": "^3.5.3",
        "sade": "^1.7.4",
        "vite": "^2.9.0"
@@ -3359,6 +3337,19 @@
        "svelte-hmr": "^0.14.11"
      }
    },
    "@sveltekit-i18n/base": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/@sveltekit-i18n/base/-/base-1.2.1.tgz",
      "integrity": "sha512-F8gqG2+KAOeT0o2wYlUrW3TRCX7zaD7rBy/1CEVNw0irfw9TgFf/ODmhubkHHT3+6Zk+SMz8RNgeuffBfAMbJw==",
      "requires": {
        "@sveltekit-i18n/parser-default": "^1.x"
      }
    },
    "@sveltekit-i18n/parser-default": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/@sveltekit-i18n/parser-default/-/parser-default-1.0.3.tgz",
      "integrity": "sha512-HheveklTjp3hxpYQhoHfyA6B4bQaUeSV5MQf2usIv/58UF2jY/YqhCAWj9bDBjufbuZc5pSz4BXvdX3WVT+viA=="
    },
    "@types/json-schema": {
      "version": "7.0.11",
      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -3366,9 +3357,9 @@
      "dev": true
    },
    "@types/node": {
      "version": "17.0.34",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.34.tgz",
      "integrity": "sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==",
      "version": "17.0.35",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.35.tgz",
      "integrity": "sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==",
      "dev": true
    },
    "@types/pug": {
@@ -3387,14 +3378,14 @@
      }
    },
    "@typescript-eslint/eslint-plugin": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.24.0.tgz",
      "integrity": "sha512-6bqFGk6wa9+6RrU++eLknKyDqXU1Oc8nyoLu5a1fU17PNRJd9UBr56rMF7c4DRaRtnarlkQ4jwxUbvBo8cNlpw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz",
      "integrity": "sha512-icYrFnUzvm+LhW0QeJNKkezBu6tJs9p/53dpPLFH8zoM9w1tfaKzVurkPotEpAqQ8Vf8uaFyL5jHd0Vs6Z0ZQg==",
      "dev": true,
      "requires": {
        "@typescript-eslint/scope-manager": "5.24.0",
        "@typescript-eslint/type-utils": "5.24.0",
        "@typescript-eslint/utils": "5.24.0",
        "@typescript-eslint/scope-manager": "5.25.0",
        "@typescript-eslint/type-utils": "5.25.0",
        "@typescript-eslint/utils": "5.25.0",
        "debug": "^4.3.4",
        "functional-red-black-tree": "^1.0.1",
        "ignore": "^5.2.0",
@@ -3404,52 +3395,52 @@
      }
    },
    "@typescript-eslint/parser": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.24.0.tgz",
      "integrity": "sha512-4q29C6xFYZ5B2CXqSBBdcS0lPyfM9M09DoQLtHS5kf+WbpV8pBBhHDLNhXfgyVwFnhrhYzOu7xmg02DzxeF2Uw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.25.0.tgz",
      "integrity": "sha512-r3hwrOWYbNKP1nTcIw/aZoH+8bBnh/Lh1iDHoFpyG4DnCpvEdctrSl6LOo19fZbzypjQMHdajolxs6VpYoChgA==",
      "dev": true,
      "requires": {
        "@typescript-eslint/scope-manager": "5.24.0",
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/typescript-estree": "5.24.0",
        "@typescript-eslint/scope-manager": "5.25.0",
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/typescript-estree": "5.25.0",
        "debug": "^4.3.4"
      }
    },
    "@typescript-eslint/scope-manager": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.24.0.tgz",
      "integrity": "sha512-WpMWipcDzGmMzdT7NtTjRXFabx10WleLUGrJpuJLGaxSqpcyq5ACpKSD5VE40h2nz3melQ91aP4Du7lh9FliCA==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.25.0.tgz",
      "integrity": "sha512-p4SKTFWj+2VpreUZ5xMQsBMDdQ9XdRvODKXN4EksyBjFp2YvQdLkyHqOffakYZPuWJUDNu3jVXtHALDyTv3cww==",
      "dev": true,
      "requires": {
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/visitor-keys": "5.24.0"
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/visitor-keys": "5.25.0"
      }
    },
    "@typescript-eslint/type-utils": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.24.0.tgz",
      "integrity": "sha512-uGi+sQiM6E5CeCZYBXiaIvIChBXru4LZ1tMoeKbh1Lze+8BO9syUG07594C4lvN2YPT4KVeIupOJkVI+9/DAmQ==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.25.0.tgz",
      "integrity": "sha512-B6nb3GK3Gv1Rsb2pqalebe/RyQoyG/WDy9yhj8EE0Ikds4Xa8RR28nHz+wlt4tMZk5bnAr0f3oC8TuDAd5CPrw==",
      "dev": true,
      "requires": {
        "@typescript-eslint/utils": "5.24.0",
        "@typescript-eslint/utils": "5.25.0",
        "debug": "^4.3.4",
        "tsutils": "^3.21.0"
      }
    },
    "@typescript-eslint/types": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.24.0.tgz",
      "integrity": "sha512-Tpg1c3shTDgTmZd3qdUyd+16r/pGmVaVEbLs+ufuWP0EruVbUiEOmpBBQxBb9a8iPRxi8Rb2oiwOxuZJzSq11A==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.25.0.tgz",
      "integrity": "sha512-7fWqfxr0KNHj75PFqlGX24gWjdV/FDBABXL5dyvBOWHpACGyveok8Uj4ipPX/1fGU63fBkzSIycEje4XsOxUFA==",
      "dev": true
    },
    "@typescript-eslint/typescript-estree": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.24.0.tgz",
      "integrity": "sha512-zcor6vQkQmZAQfebSPVwUk/FD+CvnsnlfKXYeQDsWXRF+t7SBPmIfNia/wQxCSeu1h1JIjwV2i9f5/DdSp/uDw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.25.0.tgz",
      "integrity": "sha512-MrPODKDych/oWs/71LCnuO7NyR681HuBly2uLnX3r5i4ME7q/yBqC4hW33kmxtuauLTM0OuBOhhkFaxCCOjEEw==",
      "dev": true,
      "requires": {
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/visitor-keys": "5.24.0",
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/visitor-keys": "5.25.0",
        "debug": "^4.3.4",
        "globby": "^11.1.0",
        "is-glob": "^4.0.3",
@@ -3458,26 +3449,26 @@
      }
    },
    "@typescript-eslint/utils": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.24.0.tgz",
      "integrity": "sha512-K05sbWoeCBJH8KXu6hetBJ+ukG0k2u2KlgD3bN+v+oBKm8adJqVHpSSLHNzqyuv0Lh4GVSAUgZ5lB4icmPmWLw==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.25.0.tgz",
      "integrity": "sha512-qNC9bhnz/n9Kba3yI6HQgQdBLuxDoMgdjzdhSInZh6NaDnFpTUlwNGxplUFWfY260Ya0TRPvkg9dd57qxrJI9g==",
      "dev": true,
      "requires": {
        "@types/json-schema": "^7.0.9",
        "@typescript-eslint/scope-manager": "5.24.0",
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/typescript-estree": "5.24.0",
        "@typescript-eslint/scope-manager": "5.25.0",
        "@typescript-eslint/types": "5.25.0",
        "@typescript-eslint/typescript-estree": "5.25.0",
        "eslint-scope": "^5.1.1",
        "eslint-utils": "^3.0.0"
      }
    },
    "@typescript-eslint/visitor-keys": {
      "version": "5.24.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.24.0.tgz",
      "integrity": "sha512-qzGwSXMyMnogcAo+/2fU+jhlPPVMXlIH2PeAonIKjJSoDKl1+lJVvG5Z5Oud36yU0TWK2cs1p/FaSN5J2OUFYA==",
      "version": "5.25.0",
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.25.0.tgz",
      "integrity": "sha512-yd26vFgMsC4h2dgX4+LR+GeicSKIfUvZREFLf3DDjZPtqgLx5AJZr6TetMNwFP9hcKreTTeztQYBTNbNoOycwA==",
      "dev": true,
      "requires": {
        "@typescript-eslint/types": "5.24.0",
        "@typescript-eslint/types": "5.25.0",
        "eslint-visitor-keys": "^3.3.0"
      }
    },
@@ -3594,6 +3585,11 @@
      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
      "dev": true
    },
    "base64-js": {
      "version": "1.5.1",
      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
    },
    "binary-extensions": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -3632,6 +3628,15 @@
        "picocolors": "^1.0.0"
      }
    },
    "buffer": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
      "requires": {
        "base64-js": "^1.3.1",
        "ieee754": "^1.2.1"
      }
    },
    "buffer-crc32": {
      "version": "0.2.13",
      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
@@ -3738,7 +3743,8 @@
    "deepmerge": {
      "version": "4.2.2",
      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
      "dev": true
    },
    "defined": {
      "version": "1.0.0",
@@ -4156,7 +4162,8 @@
    "estree-walker": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
      "dev": true
    },
    "esutils": {
      "version": "2.0.3",
@@ -4304,7 +4311,8 @@
    "globalyzer": {
      "version": "0.1.0",
      "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz",
      "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q=="
      "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==",
      "dev": true
    },
    "globby": {
      "version": "11.1.0",
@@ -4323,7 +4331,8 @@
    "globrex": {
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
      "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
      "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
      "dev": true
    },
    "graceful-fs": {
      "version": "4.2.10",
@@ -4346,6 +4355,11 @@
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
      "dev": true
    },
    "ieee754": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
    },
    "ignore": {
      "version": "5.2.0",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
@@ -4390,17 +4404,6 @@
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
      "dev": true
    },
    "intl-messageformat": {
      "version": "9.13.0",
      "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-9.13.0.tgz",
      "integrity": "sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw==",
      "requires": {
        "@formatjs/ecma402-abstract": "1.11.4",
        "@formatjs/fast-memoize": "1.2.1",
        "@formatjs/icu-messageformat-parser": "2.1.0",
        "tslib": "^2.1.0"
      }
    },
    "is-binary-path": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -4562,7 +4565,8 @@
    "mri": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
      "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
      "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
      "dev": true
    },
    "mrmime": {
      "version": "1.0.0",
@@ -4681,12 +4685,12 @@
      "dev": true
    },
    "postcss": {
      "version": "8.4.13",
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.13.tgz",
      "integrity": "sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==",
      "version": "8.4.14",
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
      "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
      "dev": true,
      "requires": {
        "nanoid": "^3.3.3",
        "nanoid": "^3.3.4",
        "picocolors": "^1.0.0",
        "source-map-js": "^1.0.2"
      }
@@ -4826,9 +4830,9 @@
      }
    },
    "rollup": {
      "version": "2.73.0",
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.73.0.tgz",
      "integrity": "sha512-h/UngC3S4Zt28mB3g0+2YCMegT5yoftnQplwzPqGZcKvlld5e+kT/QRmJiL+qxGyZKOYpgirWGdLyEO1b0dpLQ==",
      "version": "2.74.1",
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.74.1.tgz",
      "integrity": "sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==",
      "dev": true,
      "requires": {
        "fsevents": "~2.3.2"
@@ -4847,6 +4851,7 @@
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
      "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
      "dev": true,
      "requires": {
        "mri": "^1.1.0"
      }
@@ -4875,9 +4880,9 @@
      }
    },
    "sass": {
      "version": "1.51.0",
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.51.0.tgz",
      "integrity": "sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==",
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/sass/-/sass-1.52.0.tgz",
      "integrity": "sha512-6EnTglag2oVD8sNJCxUi2Jd3ICH9tJ5Mqudt/gIZNBR2uKJUBZuTpW9O1t04SkDLP7VFa76FCWTV2rwchqM8Kw==",
      "dev": true,
      "requires": {
        "chokidar": ">=3.0.0 <4.0.0",
@@ -5006,18 +5011,6 @@
      "dev": true,
      "requires": {}
    },
    "svelte-i18n": {
      "version": "3.4.0",
      "resolved": "https://registry.npmjs.org/svelte-i18n/-/svelte-i18n-3.4.0.tgz",
      "integrity": "sha512-590N+YIRlebDT3fXmuAxd4guQZLR3vm4kCs5UhWYmw3SxOlJNZ7HwYYiw6d4jDr7P+Cx7DSopk1Z1K9wn8B6EA==",
      "requires": {
        "deepmerge": "^4.2.2",
        "estree-walker": "^2.0.1",
        "intl-messageformat": "^9.3.15",
        "sade": "^1.7.4",
        "tiny-glob": "^0.2.6"
      }
    },
    "svelte-preprocess": {
      "version": "4.10.6",
      "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.10.6.tgz",
@@ -5043,6 +5036,15 @@
        }
      }
    },
    "sveltekit-i18n": {
      "version": "2.2.1",
      "resolved": "https://registry.npmjs.org/sveltekit-i18n/-/sveltekit-i18n-2.2.1.tgz",
      "integrity": "sha512-1CyaRN6dBvp467JjBdji+nJf+7pZ3myFu+2YaCuGSAt09Cvt5ndfRbzy+aAd5WJdk6Lu/hnPEE7ZZFauTbDRNw==",
      "requires": {
        "@sveltekit-i18n/base": "1.2.1",
        "@sveltekit-i18n/parser-default": "1.0.3"
      }
    },
    "tailwindcss": {
      "version": "3.0.24",
      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.0.24.tgz",
@@ -5093,6 +5095,7 @@
      "version": "0.2.9",
      "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
      "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==",
      "dev": true,
      "requires": {
        "globalyzer": "0.1.0",
        "globrex": "^0.1.2"
@@ -5110,7 +5113,8 @@
    "tslib": {
      "version": "2.4.0",
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
      "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
      "dev": true
    },
    "tsutils": {
      "version": "3.21.0",
diff --git a/frontend/package.json b/frontend/package.json
index f7ca7df..953906a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -34,6 +34,7 @@
  },
  "type": "module",
  "dependencies": {
    "svelte-i18n": "^3.4.0"
    "buffer": "^6.0.3",
    "sveltekit-i18n": "^2.2.1"
  }
}
diff --git a/frontend/src/hooks.ts b/frontend/src/hooks.ts
deleted file mode 100644
index 3979f96..0000000
--- a/frontend/src/hooks.ts
+++ /dev/null
@@ -1 +0,0 @@
import "./i18n";
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
deleted file mode 100644
index 96b81f7..0000000
--- a/frontend/src/i18n.ts
+++ /dev/null
@@ -1,8 +0,0 @@
import { getLocaleFromNavigator, init, register } from "svelte-i18n";

register("en", () => import("./i18n/en.json"));

init({
  fallbackLocale: "en",
  initialLocale: getLocaleFromNavigator(),
});
diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json
deleted file mode 100644
index 90dc94d..0000000
--- a/frontend/src/i18n/en.json
+++ /dev/null
@@ -1,29 +0,0 @@
{
  "site_name": "blocks.ls",
  "nav": {
    "dashboard": "Dashboard",
    "blocks": "Blocks",
    "txns": "Transactions"
  },
  "home": {
    "latest_blocks": {
      "header": "Latest Blocks",
      "table": {
        "height": "Height",
        "timestamp": "Timestamp",
        "txns": "Transactions",
        "size": "Size (KB)",
        "weight": "Weight (KWU)"
      }
    },
    "latest_txns": {
      "header": "Latest Transactions",
      "table": {
        "txn_id": "Transaction ID",
        "value": "Value",
        "size": "Size",
        "fee": "Fee"
      }
    }
  }
}
diff --git a/frontend/src/lib/bitcoinScript.ts b/frontend/src/lib/bitcoinScript.ts
new file mode 100644
index 0000000..28d9d06
--- /dev/null
+++ b/frontend/src/lib/bitcoinScript.ts
@@ -0,0 +1,148 @@
import { Buffer } from "buffer/";

export function hexToAsm(hex: string) {
    const bytes = Buffer.from(hex, 'hex');

    const out = [];

    // adapted from https://gist.github.com/rsbondi/674ec5ac823a244b889773be57304f85
    for (let i = 0; i < bytes.length; i++) {
        const byte = bytes[i];

        if (byte < 0x02) {
            out.push(byte);
            continue;
        }

        if (byte >= 0x52 && byte <= 0x60)  {
            out.push(byte-0x50)
            continue;
        }

        if(byte >= 0x02 && byte <= 0x4b) {
            out.push(bytes.slice(i + 1, i + 1 + byte).toString("hex"));
            i += byte;
        }

        out.push(Operation[byte] || byte.toString(16))
    }

    return out;
}

enum Operation {
    OP_FALSE = 0x00,
    OP_PUSHDATA1 = 0x4c,
    OP_PUSHDATA2 = 0x4d,
    OP_PUSHDATA4 = 0x4e,
    OP_1NEGATE = 0x4f,
    OP_RESERVED = 0x50,
    OP_TRUE = 0x51,
    OP_2 = 0x52,
    OP_3 = 0x53,
    OP_4 = 0x54,
    OP_5 = 0x55,
    OP_6 = 0x56,
    OP_7 = 0x57,
    OP_8 = 0x58,
    OP_9 = 0x59,
    OP_10 = 0x5a,
    OP_11 = 0x5b,
    OP_12 = 0x5c,
    OP_13 = 0x5d,
    OP_14 = 0x5e,
    OP_15 = 0x5f,
    OP_16 = 0x60,
    OP_NOP = 0x61,
    OP_VER = 0x62,
    OP_IF = 0x63,
    OP_NOTIF = 0x64,
    OP_VERIF = 0x65,
    OP_VERNOTIF = 0x66,
    OP_ELSE = 0x67,
    OP_ENDIF = 0x68,
    OP_VERIFY = 0x69,
    OP_RETURN = 0x6a,
    OP_TOALTSTACK = 0x6b,
    OP_FROMALTSTACK = 0x6c,
    OP_2DROP = 0x6d,
    OP_2DUP = 0x6e,
    OP_3DUP = 0x6f,
    OP_2OVER = 0x70,
    OP_2ROT = 0x71,
    OP_2SWAP = 0x72,
    OP_IFDUP = 0x73,
    OP_DEPTH = 0x74,
    OP_DROP = 0x75,
    OP_DUP = 0x76,
    OP_NIP = 0x77,
    OP_OVER = 0x78,
    OP_PICK = 0x79,
    OP_ROLL = 0x7a,
    OP_ROT = 0x7b,
    OP_SWAP = 0x7c,
    OP_TUCK = 0x7d,
    OP_CAT = 0x7e,
    OP_SUBSTR = 0x7f,
    OP_LEFT = 0x80,
    OP_RIGHT = 0x81,
    OP_SIZE = 0x82,
    OP_INVERT = 0x83,
    OP_AND = 0x84,
    OP_OR = 0x85,
    OP_XOR = 0x86,
    OP_EQUAL = 0x87,
    OP_EQUALVERIFY = 0x88,
    OP_RESERVED1 = 0x89,
    OP_RESERVED2 = 0x8a,
    OP_1ADD = 0x8b,
    OP_1SUB = 0x8c,
    OP_2MUL = 0x8d,
    OP_2DIV = 0x8e,
    OP_NEGATE = 0x8f,
    OP_ABS = 0x90,
    OP_NOT = 0x91,
    OP_0NOTEQUAL = 0x92,
    OP_ADD = 0x93,
    OP_SUB = 0x94,
    OP_MUL = 0x95,
    OP_DIV = 0x96,
    OP_MOD = 0x97,
    OP_LSHIFT = 0x98,
    OP_RSHIFT = 0x99,
    OP_BOOLAND = 0x9a,
    OP_BOOLOR = 0x9b,
    OP_NUMEQUAL = 0x9c,
    OP_NUMEQUALVERIFY = 0x9d,
    OP_NUMNOTEQUAL = 0x9e,
    OP_LESSTHAN = 0x9f,
    OP_GREATERTHAN = 0xa0,
    OP_LESSTHANOREQUAL = 0xa1,
    OP_GREATERTHANOREQUAL = 0xa2,
    OP_MIN = 0xa3,
    OP_MAX = 0xa4,
    OP_WITHIN = 0xa5,
    OP_RIPEMD160 = 0xa6,
    OP_SHA1 = 0xa7,
    OP_SHA256 = 0xa8,
    OP_HASH160 = 0xa9,
    OP_HASH256 = 0xaa,
    OP_CODESEPARATOR = 0xab,
    OP_CHECKSIG = 0xac,
    OP_CHECKSIGVERIFY = 0xad,
    OP_CHECKMULTISIG = 0xae,
    OP_CHECKMULTISIGVERIFY = 0xaf,
    OP_NOP1 = 0xb0,
    OP_CHECKLOCKTIMEVERIFY = 0xb1,
    OP_CHECKSEQUENCEVERIFY = 0xb2,
    OP_NOP4 = 0xb3,
    OP_NOP5 = 0xb4,
    OP_NOP6 = 0xb5,
    OP_NOP7 = 0xb6,
    OP_NOP8 = 0xb7,
    OP_NOP9 = 0xb8,
    OP_NOP10 = 0xb9,
    OP_PUBKEYHASH = 0xfd,
    OP_PUBKEY = 0xfe,
    OP_INVALIDOPCODE = 0xff,
}
diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts
new file mode 100644
index 0000000..c4eb7e2
--- /dev/null
+++ b/frontend/src/lib/i18n.ts
@@ -0,0 +1,16 @@
import i18n from 'sveltekit-i18n';

/** @type {import('sveltekit-i18n').Config} */
const config = ({
    loaders: [
        {
            locale: 'en',
            key: '',
            loader: async () => (
                await import('./i18n/en.json')
            ).default,
        }
    ],
});

export const { t, locale, locales, loading, loadTranslations } = new i18n(config);
diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json
new file mode 100644
index 0000000..90dc94d
--- /dev/null
+++ b/frontend/src/lib/i18n/en.json
@@ -0,0 +1,29 @@
{
  "site_name": "blocks.ls",
  "nav": {
    "dashboard": "Dashboard",
    "blocks": "Blocks",
    "txns": "Transactions"
  },
  "home": {
    "latest_blocks": {
      "header": "Latest Blocks",
      "table": {
        "height": "Height",
        "timestamp": "Timestamp",
        "txns": "Transactions",
        "size": "Size (KB)",
        "weight": "Weight (KWU)"
      }
    },
    "latest_txns": {
      "header": "Latest Transactions",
      "table": {
        "txn_id": "Transaction ID",
        "value": "Value",
        "size": "Size",
        "fee": "Fee"
      }
    }
  }
}
diff --git a/frontend/src/routes/__layout.svelte b/frontend/src/routes/__layout.svelte
index 7e7a811..aa16b20 100644
--- a/frontend/src/routes/__layout.svelte
+++ b/frontend/src/routes/__layout.svelte
@@ -1,5 +1,20 @@
<script context="module">
  import { locale, loadTranslations } from '$lib/i18n';

  export async function load({ url }) {
    const { pathname } = url;

    const defaultLocale = 'en'; // TODO: get from cookie, user session, ...
    const initLocale = locale.get() || defaultLocale;

    await loadTranslations(initLocale, pathname);

    return {};
  }
</script>

<script>
  import { _ } from "svelte-i18n";
  import { t as _ } from "$lib/i18n";
  import "../global.scss";
  import { page } from "$app/stores";
</script>
diff --git a/frontend/src/routes/block/[id].svelte b/frontend/src/routes/block/[id].svelte
index ffcf3b9..7301541 100644
--- a/frontend/src/routes/block/[id].svelte
+++ b/frontend/src/routes/block/[id].svelte
@@ -1,7 +1,101 @@
<section>
  <h2>single block</h2>
</section>
<script context="module">
  export async function load({ fetch, params }) {
    let res = await fetch(`http://localhost:3001/block/${params.id}`);

    if (res.ok) {
      return {
        props: {
          block: await res.json()
        }
      };
    }
    return {
      status: res.status,
      error: new Error()
    };
  }
</script>

<script>
  import { hexToAsm } from "$lib/bitcoinScript";

  export let block = {};

  for (let transaction of block.transactions) {
    for (let output of transaction.outputs) {
      console.log(hexToAsm(output.script));
    }
  }
</script>

<div>
  <section class="p-7">
    <h2 class="!p-0 !py-4">Block {block.height}</h2>
    <p>{block.hash}</p>
  </section>

  <section>
    <table>
      <tbody>
        <tr>
          <th>Height</th>
          <td>{block.height}</td>
        </tr>
        <tr>
          <th>Merkle Root Hash</th>
          <td>{block.merkle_root_hash}</td>
        </tr>
      </tbody>
    </table>
  </section>

  <section class="!bg-transparent">
    <h3 class="text-white text-2xl">{block.transactions.length} Transactions</h3>
  </section>

  {#each block.transactions as transaction}
    <section>
      <h3 class="text-lg m-2">{transaction.hash}</h3>

      <div class="flex">
        <table>
          <tbody>
          {#if transaction.coinbase}
            <tr>
              <td>Coinbase</td>
            </tr>
          {:else}
            {#each transaction.inputs as input}
              <tr>
                <td>{input.previous_output?.address || hexToAsm(input.script).join('\n')}</td>
              </tr>
            {/each}
          {/if}
          </tbody>
        </table>

        <div class="text-2xl mx-4 self-center">
        </div>

        <table>
          <tbody>
          <tr>
            {#each transaction.outputs as output}
              <td>{output.address || hexToAsm(output.script).join('\n')}</td>
            {/each}
          </tbody>
        </table>
      </div>
    </section>
  {/each}
</div>

<style lang="scss">
  @import "../../_section.scss";
  @import "../../_table.scss";

  section {
    @apply text-xs;
  }
</style>
diff --git a/frontend/src/routes/index.svelte b/frontend/src/routes/index.svelte
index 2a855a0..abd6eaf 100644
--- a/frontend/src/routes/index.svelte
+++ b/frontend/src/routes/index.svelte
@@ -1,22 +1,26 @@
<script context="module">
  export async function load({ fetch }) {
    let res = await fetch('http://localhost:3001/block');

    if (res.ok) {
      return {
        props: {
          blocks: await res.json()
        }
      };
    }
    return {
      status: res.status,
      error: new Error()
    };
  }
</script>

<script>
  import { _ } from "svelte-i18n";
  import { t as _ } from "$lib/i18n";

  let blocks = [
    {
      height: 123457,
      timestamp: "12 minutes ago",
      transactions: 453,
      size: "1094.092",
      weight: "3999.574",
    },
    {
      height: 123456,
      timestamp: "22 minutes ago",
      transactions: 133,
      size: "598.233",
      weight: "3999.122",
    },
  ];
  // TODO: needs loader
  export let blocks = [];

  let transactions = [
    {
@@ -74,9 +78,9 @@
        {#each blocks as block}
          <tr>
            <th><a href={`/block/${block.height}`}>{block.height}</a></th>
            <td>{block.timestamp}</td>
            <td>{block.transactions}</td>
            <td>{block.size}</td>
            <td>{block.timestamp}</td> <!-- todo: moment.js -->
            <td>{block.tx_count}</td>
            <td>{block.bits}</td> <!-- todo: this isn't really size -->
            <td>{block.weight}</td>
          </tr>
        {/each}
diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js
index 77bd0de..8e9699b 100644
--- a/frontend/svelte.config.js
+++ b/frontend/svelte.config.js
@@ -10,6 +10,7 @@ const config = {
  }),

  kit: {
    // TODO: adapter-cloudflare
    adapter: adapter(),
  },
};
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..320b9bc
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
{
  "name": "list-blocks",
  "lockfileVersion": 2,
  "requires": true,
  "packages": {}
}
diff --git a/web-api/src/database/blocks.rs b/web-api/src/database/blocks.rs
index bae9d0c..332ed68 100644
--- a/web-api/src/database/blocks.rs
+++ b/web-api/src/database/blocks.rs
@@ -4,6 +4,7 @@ use tokio_postgres::Row;

#[derive(Debug)]
pub struct Block {
    pub hash: Vec<u8>,
    pub id: i64,
    pub height: i64,
    pub version: i32,
@@ -11,13 +12,14 @@ pub struct Block {
    pub merkle_root_hash: Vec<u8>,
    pub timestamp: NaiveDateTime,
    pub bits: i32,
    pub nonce: i32,
    pub nonce: u32,
    pub difficulty: i64,
}

impl Block {
    pub fn from_row(row: Row) -> Result<Self> {
        Ok(Self {
            hash: row.try_get("hash")?,
            id: row.try_get("id")?,
            height: row.try_get("height")?,
            version: row.try_get("version")?,
@@ -25,7 +27,7 @@ impl Block {
            merkle_root_hash: row.try_get("merkle_root_hash")?,
            timestamp: row.try_get("timestamp")?,
            bits: row.try_get("bits")?,
            nonce: row.try_get("nonce")?,
            nonce: row.try_get::<_, i32>("nonce")? as u32, // TODO
            difficulty: row.try_get("difficulty")?,
        })
    }
@@ -39,6 +41,31 @@ pub async fn fetch_height(db: &Connection) -> Result<u64> {
    Ok(u64::try_from(height)?)
}

pub type TransactionCount = i64;

pub async fn fetch_latest_blocks(db: &Connection, count: i64) -> Result<Vec<(Block, TransactionCount)>> {
    let blocks = db
        .query(
            "SELECT blocks.*, COUNT(transactions.id) AS tx_count
             FROM blocks
             LEFT JOIN transactions
               ON transactions.block_id = blocks.id
             GROUP BY blocks.id
             ORDER BY blocks.height DESC
             LIMIT $1",
            &[&count],
        )
        .await?;

    blocks
        .into_iter()
        .map(|row| {
            let tx_count = row.try_get("tx_count")?;
            Ok((Block::from_row(row)?, tx_count))
        })
        .collect::<Result<Vec<_>>>()
}

pub async fn fetch_block_by_height(db: &Connection, height: i64) -> Result<Option<Block>> {
    let block = db
        .query_opt(
diff --git a/web-api/src/database/transactions.rs b/web-api/src/database/transactions.rs
index 1db5cb2..e1198e3 100644
--- a/web-api/src/database/transactions.rs
+++ b/web-api/src/database/transactions.rs
@@ -1,5 +1,6 @@
use crate::database::{Connection, Result};
use serde::Deserialize;
use serde::{Deserialize, Deserializer};
use serde::de::Error;
use tokio::time::Instant;
use tokio_postgres::types::Json;
use tokio_postgres::Row;
@@ -34,17 +35,26 @@ impl Transaction {
#[derive(Deserialize, Debug)]
pub struct TransactionInput {
    pub previous_output_tx: Option<TransactionOutput>,
    #[serde(deserialize_with = "trim_hex_prefix")]
    pub script: String,
}

#[derive(Deserialize, Debug)]
pub struct TransactionOutput {
    pub value: i64,
    #[serde(deserialize_with = "trim_hex_prefix")]
    pub script: String,
    pub unspendable: bool,
    pub address: Option<String>,
}

fn trim_hex_prefix<'de, D: Deserializer<'de>>(deserializer: D) -> std::result::Result<String, D::Error> {
    let mut s = String::deserialize(deserializer)?;
    s.remove(0);
    s.remove(0);
    Ok(s)
}

pub async fn fetch_transactions_for_block(db: &Connection, id: i64) -> Result<Vec<Transaction>> {
    let transactions = db
        .query(
diff --git a/web-api/src/methods/block.rs b/web-api/src/methods/block.rs
index 25a5587..d15fdaf 100644
--- a/web-api/src/methods/block.rs
+++ b/web-api/src/methods/block.rs
@@ -1,3 +1,4 @@
use std::ptr::hash;
use crate::Database;
use axum::extract::Path;
use axum::{Extension, Json};
@@ -5,6 +6,44 @@ use chrono::NaiveDateTime;
use serde::Serialize;

#[derive(Serialize)]
pub struct BlockList {
    hash: String,
    height: i64,
    version: i32,
    timestamp: NaiveDateTime,
    bits: i32,
    nonce: u32,
    difficulty: i64,
    tx_count: i64,
}

pub async fn list(Extension(database): Extension<Database>) -> Json<Vec<BlockList>> {
    let database = database.get().await.unwrap();

    let blocks = crate::database::blocks::fetch_latest_blocks(&database, 5).await.unwrap();

    Json(
        blocks.into_iter()
            .map(|(mut block, tx_count)| {
                // TODO: do this on insert
                block.hash.reverse();

                BlockList {
                    hash: hex::encode(block.hash),
                    height: block.height,
                    version: block.version,
                    timestamp: block.timestamp,
                    bits: block.bits,
                    nonce: block.nonce,
                    difficulty: block.difficulty,
                    tx_count
                }
            })
            .collect()
    )
}

#[derive(Serialize)]
pub struct Block {
    height: i64,
    version: i32,
@@ -13,9 +52,10 @@ pub struct Block {
    // #[serde(with = "chrono::serde::ts_seconds")]
    timestamp: NaiveDateTime,
    bits: i32,
    nonce: i32,
    nonce: u32,
    difficulty: i64,
    transactions: Vec<Transaction>,
    hash: String,
}

#[derive(Serialize)]
@@ -70,7 +110,7 @@ pub async fn handle(
) -> Json<Block> {
    let database = database.get().await.unwrap();

    let block = crate::database::blocks::fetch_block_by_height(&database, height)
    let mut block = crate::database::blocks::fetch_block_by_height(&database, height)
        .await
        .unwrap()
        .unwrap();
@@ -80,7 +120,11 @@ pub async fn handle(
            .await
            .unwrap();

    // TODO: do this on insert
    block.hash.reverse();

    Json(Block {
        hash: hex::encode(block.hash),
        height: block.height,
        version: block.version,
        size: block.size,
diff --git a/web-api/src/methods/mod.rs b/web-api/src/methods/mod.rs
index 6ee689d..1e3ef08 100644
--- a/web-api/src/methods/mod.rs
+++ b/web-api/src/methods/mod.rs
@@ -7,5 +7,6 @@ mod height;
pub fn router() -> Router {
    Router::new()
        .route("/height", get(height::handle))
        .route("/block", get(block::list))
        .route("/block/:height", get(block::handle))
}