Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Remove Ethereum functionality
Sebastian Martinez committed 3 years ago
commit 25135c7025fc1bb44dbb810755e06adea924456a
parent 3d018dae1830f4ac0bd8473b4c58455833c1064c
68 files changed +645 -7502
modified package-lock.json
@@ -8,30 +8,22 @@
      "version": "1.0.0",
      "hasInstallScript": true,
      "dependencies": {
-
        "@ethersproject/abstract-provider": "^5.7.0",
        "@radicle/gray-matter": "4.1.0",
-
        "@stardazed/streams": "^3.1.0",
-
        "@walletconnect/client": "^1.8.0",
        "@wooorm/starry-night": "^1.5.0",
        "buffer": "^6.0.3",
        "dompurify": "^2.4.3",
-
        "ethers": "^5.7.2",
-
        "events": "^3.3.0",
        "hast-util-to-dom": "^3.1.1",
        "hast-util-to-html": "^8.0.4",
        "katex": "^0.16.4",
        "lodash": "^4.17.21",
-
        "lru-cache": "^7.14.1",
        "marked": "^4.2.12",
        "md5": "^2.3.0",
        "plausible-tracker": "^0.3.8",
-
        "pure-svg-code": "^1.0.6",
        "svelte": "^3.55.1",
-
        "twemoji": "^14.0.2",
-
        "util": "^0.12.5"
+
        "twemoji": "^14.0.2"
      },
      "devDependencies": {
-
        "@playwright/test": "^1.29.2",
+
        "@playwright/test": "^1.30.0",
        "@sinonjs/fake-timers": "^10.0.2",
        "@sveltejs/vite-plugin-svelte": "^2.0.2",
        "@tsconfig/svelte": "^3.0.0",
@@ -42,27 +34,26 @@
        "@types/md5": "^2.3.2",
        "@types/node": "^18.11.18",
        "@types/sinonjs__fake-timers": "^8.1.2",
-
        "@typescript-eslint/eslint-plugin": "^5.48.2",
+
        "@typescript-eslint/eslint-plugin": "^5.49.0",
        "chalk": "^5.2.0",
-
        "eslint": "^8.32.0",
+
        "eslint": "^8.33.0",
        "eslint-plugin-svelte3": "^4.0.0",
-
        "happy-dom": "^8.1.4",
+
        "happy-dom": "^8.2.0",
        "prettier": "^2.8.3",
        "prettier-plugin-svelte": "^2.9.0",
-
        "svelte-check": "^3.0.2",
-
        "tslib": "^2.4.1",
+
        "svelte-check": "^3.0.3",
        "typescript": "^4.9.4",
        "vite": "^4.0.4",
-
        "vitest": "^0.27.1"
+
        "vitest": "^0.28.3"
      },
      "engines": {
        "node": ">=18.12.1"
      }
    },
    "node_modules/@esbuild/android-arm": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.16.tgz",
-
      "integrity": "sha512-BUuWMlt4WSXod1HSl7aGK8fJOsi+Tab/M0IDK1V1/GstzoOpqc/v3DqmN8MkuapPKQ9Br1WtLAN4uEgWR8x64A==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz",
+
      "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==",
      "cpu": [
        "arm"
      ],
@@ -76,9 +67,9 @@
      }
    },
    "node_modules/@esbuild/android-arm64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.16.tgz",
-
      "integrity": "sha512-hFHVAzUKp9Tf8psGq+bDVv+6hTy1bAOoV/jJMUWwhUnIHsh6WbFMhw0ZTkqDuh7TdpffFoHOiIOIxmHc7oYRBQ==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz",
+
      "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==",
      "cpu": [
        "arm64"
      ],
@@ -92,9 +83,9 @@
      }
    },
    "node_modules/@esbuild/android-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.16.tgz",
-
      "integrity": "sha512-9WhxJpeb6XumlfivldxqmkJepEcELekmSw3NkGrs+Edq6sS5KRxtUBQuKYDD7KqP59dDkxVbaoPIQFKWQG0KLg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz",
+
      "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==",
      "cpu": [
        "x64"
      ],
@@ -108,9 +99,9 @@
      }
    },
    "node_modules/@esbuild/darwin-arm64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.16.tgz",
-
      "integrity": "sha512-8Z+wld+vr/prHPi2O0X7o1zQOfMbXWGAw9hT0jEyU/l/Yrg+0Z3FO9pjPho72dVkZs4ewZk0bDOFLdZHm8jEfw==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz",
+
      "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==",
      "cpu": [
        "arm64"
      ],
@@ -124,9 +115,9 @@
      }
    },
    "node_modules/@esbuild/darwin-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.16.tgz",
-
      "integrity": "sha512-CYkxVvkZzGCqFrt7EgjFxQKhlUPyDkuR9P0Y5wEcmJqVI8ncerOIY5Kej52MhZyzOBXkYrJgZeVZC9xXXoEg9A==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz",
+
      "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==",
      "cpu": [
        "x64"
      ],
@@ -140,9 +131,9 @@
      }
    },
    "node_modules/@esbuild/freebsd-arm64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.16.tgz",
-
      "integrity": "sha512-fxrw4BYqQ39z/3Ja9xj/a1gMsVq0xEjhSyI4a9MjfvDDD8fUV8IYliac96i7tzZc3+VytyXX+XNsnpEk5sw5Wg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz",
+
      "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==",
      "cpu": [
        "arm64"
      ],
@@ -156,9 +147,9 @@
      }
    },
    "node_modules/@esbuild/freebsd-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.16.tgz",
-
      "integrity": "sha512-8p3v1D+du2jiDvSoNVimHhj7leSfST9YlKsAEO7etBfuqjaBMndo0fmjNLp0JCMld+XIx9L80tooOkyUv1a1PQ==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz",
+
      "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==",
      "cpu": [
        "x64"
      ],
@@ -172,9 +163,9 @@
      }
    },
    "node_modules/@esbuild/linux-arm": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.16.tgz",
-
      "integrity": "sha512-bYaocE1/PTMRmkgSckZ0D0Xn2nox8v2qlk+MVVqm+VECNKDdZvghVZtH41dNtBbwADSvA6qkCHGYeWm9LrNCBw==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz",
+
      "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==",
      "cpu": [
        "arm"
      ],
@@ -188,9 +179,9 @@
      }
    },
    "node_modules/@esbuild/linux-arm64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.16.tgz",
-
      "integrity": "sha512-N3u6BBbCVY3xeP2D8Db7QY8I+nZ+2AgOopUIqk+5yCoLnsWkcVxD2ay5E9iIdvApFi1Vg1lZiiwaVp8bOpAc4A==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz",
+
      "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==",
      "cpu": [
        "arm64"
      ],
@@ -204,9 +195,9 @@
      }
    },
    "node_modules/@esbuild/linux-ia32": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.16.tgz",
-
      "integrity": "sha512-dxjqLKUW8GqGemoRT9v8IgHk+T4tRm1rn1gUcArsp26W9EkK/27VSjBVUXhEG5NInHZ92JaQ3SSMdTwv/r9a2A==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz",
+
      "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==",
      "cpu": [
        "ia32"
      ],
@@ -220,9 +211,9 @@
      }
    },
    "node_modules/@esbuild/linux-loong64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.16.tgz",
-
      "integrity": "sha512-MdUFggHjRiCCwNE9+1AibewoNq6wf94GLB9Q9aXwl+a75UlRmbRK3h6WJyrSGA6ZstDJgaD2wiTSP7tQNUYxwA==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz",
+
      "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==",
      "cpu": [
        "loong64"
      ],
@@ -236,9 +227,9 @@
      }
    },
    "node_modules/@esbuild/linux-mips64el": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.16.tgz",
-
      "integrity": "sha512-CO3YmO7jYMlGqGoeFeKzdwx/bx8Vtq/SZaMAi+ZLDUnDUdfC7GmGwXzIwDJ70Sg+P9pAemjJyJ1icKJ9R3q/Fg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz",
+
      "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==",
      "cpu": [
        "mips64el"
      ],
@@ -252,9 +243,9 @@
      }
    },
    "node_modules/@esbuild/linux-ppc64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.16.tgz",
-
      "integrity": "sha512-DSl5Czh5hCy/7azX0Wl9IdzPHX2H8clC6G87tBnZnzUpNgRxPFhfmArbaHoAysu4JfqCqbB/33u/GL9dUgCBAw==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz",
+
      "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==",
      "cpu": [
        "ppc64"
      ],
@@ -268,9 +259,9 @@
      }
    },
    "node_modules/@esbuild/linux-riscv64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.16.tgz",
-
      "integrity": "sha512-sSVVMEXsqf1fQu0j7kkhXMViroixU5XoaJXl1u/u+jbXvvhhCt9YvA/B6VM3aM/77HuRQ94neS5bcisijGnKFQ==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz",
+
      "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==",
      "cpu": [
        "riscv64"
      ],
@@ -284,9 +275,9 @@
      }
    },
    "node_modules/@esbuild/linux-s390x": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.16.tgz",
-
      "integrity": "sha512-jRqBCre9gZGoCdCN/UWCCMwCMsOg65IpY9Pyj56mKCF5zXy9d60kkNRdDN6YXGjr3rzcC4DXnS/kQVCGcC4yPQ==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz",
+
      "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==",
      "cpu": [
        "s390x"
      ],
@@ -300,9 +291,9 @@
      }
    },
    "node_modules/@esbuild/linux-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.16.tgz",
-
      "integrity": "sha512-G1+09TopOzo59/55lk5Q0UokghYLyHTKKzD5lXsAOOlGDbieGEFJpJBr3BLDbf7cz89KX04sBeExAR/pL/26sA==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz",
+
      "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==",
      "cpu": [
        "x64"
      ],
@@ -316,9 +307,9 @@
      }
    },
    "node_modules/@esbuild/netbsd-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.16.tgz",
-
      "integrity": "sha512-xwjGJB5wwDEujLaJIrSMRqWkbigALpBNcsF9SqszoNKc+wY4kPTdKrSxiY5ik3IatojePP+WV108MvF6q6np4w==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz",
+
      "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==",
      "cpu": [
        "x64"
      ],
@@ -332,9 +323,9 @@
      }
    },
    "node_modules/@esbuild/openbsd-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.16.tgz",
-
      "integrity": "sha512-yeERkoxG2nR2oxO5n+Ms7MsCeNk23zrby2GXCqnfCpPp7KNc0vxaaacIxb21wPMfXXRhGBrNP4YLIupUBrWdlg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz",
+
      "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==",
      "cpu": [
        "x64"
      ],
@@ -348,9 +339,9 @@
      }
    },
    "node_modules/@esbuild/sunos-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.16.tgz",
-
      "integrity": "sha512-nHfbEym0IObXPhtX6Va3H5GaKBty2kdhlAhKmyCj9u255ktAj0b1YACUs9j5H88NRn9cJCthD1Ik/k9wn8YKVg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz",
+
      "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==",
      "cpu": [
        "x64"
      ],
@@ -364,9 +355,9 @@
      }
    },
    "node_modules/@esbuild/win32-arm64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.16.tgz",
-
      "integrity": "sha512-pdD+M1ZOFy4hE15ZyPX09fd5g4DqbbL1wXGY90YmleVS6Y5YlraW4BvHjim/X/4yuCpTsAFvsT4Nca2lbyDH/A==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz",
+
      "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==",
      "cpu": [
        "arm64"
      ],
@@ -380,9 +371,9 @@
      }
    },
    "node_modules/@esbuild/win32-ia32": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.16.tgz",
-
      "integrity": "sha512-IPEMfU9p0c3Vb8PqxaPX6BM9rYwlTZGYOf9u+kMdhoILZkVKEjq6PKZO0lB+isojWwAnAqh4ZxshD96njTXajg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz",
+
      "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==",
      "cpu": [
        "ia32"
      ],
@@ -396,9 +387,9 @@
      }
    },
    "node_modules/@esbuild/win32-x64": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.16.tgz",
-
      "integrity": "sha512-1YYpoJ39WV/2bnShPwgdzJklc+XS0bysN6Tpnt1cWPdeoKOG4RMEY1g7i534QxXX/rPvNx/NLJQTTCeORYzipg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz",
+
      "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==",
      "cpu": [
        "x64"
      ],
@@ -434,702 +425,6 @@
        "url": "https://opencollective.com/eslint"
      }
    },
-
    "node_modules/@ethersproject/abi": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz",
-
      "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/constants": "^5.7.0",
-
        "@ethersproject/hash": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/abstract-provider": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz",
-
      "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/networks": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/transactions": "^5.7.0",
-
        "@ethersproject/web": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/abstract-signer": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz",
-
      "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abstract-provider": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/address": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz",
-
      "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/rlp": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/base64": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz",
-
      "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/basex": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz",
-
      "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/bignumber": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz",
-
      "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "bn.js": "^5.2.1"
-
      }
-
    },
-
    "node_modules/@ethersproject/bytes": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz",
-
      "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/constants": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz",
-
      "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bignumber": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/contracts": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz",
-
      "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abi": "^5.7.0",
-
        "@ethersproject/abstract-provider": "^5.7.0",
-
        "@ethersproject/abstract-signer": "^5.7.0",
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/constants": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/transactions": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/hash": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz",
-
      "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abstract-signer": "^5.7.0",
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/base64": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/hdnode": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz",
-
      "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abstract-signer": "^5.7.0",
-
        "@ethersproject/basex": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/pbkdf2": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/sha2": "^5.7.0",
-
        "@ethersproject/signing-key": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0",
-
        "@ethersproject/transactions": "^5.7.0",
-
        "@ethersproject/wordlists": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/json-wallets": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz",
-
      "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abstract-signer": "^5.7.0",
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/hdnode": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/pbkdf2": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/random": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0",
-
        "@ethersproject/transactions": "^5.7.0",
-
        "aes-js": "3.0.0",
-
        "scrypt-js": "3.0.1"
-
      }
-
    },
-
    "node_modules/@ethersproject/json-wallets/node_modules/aes-js": {
-
      "version": "3.0.0",
-
      "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
-
      "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw=="
-
    },
-
    "node_modules/@ethersproject/keccak256": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz",
-
      "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "js-sha3": "0.8.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/logger": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz",
-
      "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ]
-
    },
-
    "node_modules/@ethersproject/networks": {
-
      "version": "5.7.1",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz",
-
      "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/pbkdf2": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz",
-
      "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/sha2": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/properties": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz",
-
      "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/providers": {
-
      "version": "5.7.2",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz",
-
      "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abstract-provider": "^5.7.0",
-
        "@ethersproject/abstract-signer": "^5.7.0",
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/base64": "^5.7.0",
-
        "@ethersproject/basex": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/constants": "^5.7.0",
-
        "@ethersproject/hash": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/networks": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/random": "^5.7.0",
-
        "@ethersproject/rlp": "^5.7.0",
-
        "@ethersproject/sha2": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0",
-
        "@ethersproject/transactions": "^5.7.0",
-
        "@ethersproject/web": "^5.7.0",
-
        "bech32": "1.1.4",
-
        "ws": "7.4.6"
-
      }
-
    },
-
    "node_modules/@ethersproject/providers/node_modules/ws": {
-
      "version": "7.4.6",
-
      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
-
      "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
-
      "engines": {
-
        "node": ">=8.3.0"
-
      },
-
      "peerDependencies": {
-
        "bufferutil": "^4.0.1",
-
        "utf-8-validate": "^5.0.2"
-
      },
-
      "peerDependenciesMeta": {
-
        "bufferutil": {
-
          "optional": true
-
        },
-
        "utf-8-validate": {
-
          "optional": true
-
        }
-
      }
-
    },
-
    "node_modules/@ethersproject/random": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz",
-
      "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/rlp": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz",
-
      "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/sha2": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz",
-
      "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "hash.js": "1.1.7"
-
      }
-
    },
-
    "node_modules/@ethersproject/signing-key": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz",
-
      "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "bn.js": "^5.2.1",
-
        "elliptic": "6.5.4",
-
        "hash.js": "1.1.7"
-
      }
-
    },
-
    "node_modules/@ethersproject/solidity": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz",
-
      "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/sha2": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/strings": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz",
-
      "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/constants": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/transactions": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz",
-
      "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/constants": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/rlp": "^5.7.0",
-
        "@ethersproject/signing-key": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/units": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz",
-
      "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/constants": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/wallet": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz",
-
      "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abstract-provider": "^5.7.0",
-
        "@ethersproject/abstract-signer": "^5.7.0",
-
        "@ethersproject/address": "^5.7.0",
-
        "@ethersproject/bignumber": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/hash": "^5.7.0",
-
        "@ethersproject/hdnode": "^5.7.0",
-
        "@ethersproject/json-wallets": "^5.7.0",
-
        "@ethersproject/keccak256": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/random": "^5.7.0",
-
        "@ethersproject/signing-key": "^5.7.0",
-
        "@ethersproject/transactions": "^5.7.0",
-
        "@ethersproject/wordlists": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/web": {
-
      "version": "5.7.1",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz",
-
      "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/base64": "^5.7.0",
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0"
-
      }
-
    },
-
    "node_modules/@ethersproject/wordlists": {
-
      "version": "5.7.0",
-
      "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz",
-
      "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/bytes": "^5.7.0",
-
        "@ethersproject/hash": "^5.7.0",
-
        "@ethersproject/logger": "^5.7.0",
-
        "@ethersproject/properties": "^5.7.0",
-
        "@ethersproject/strings": "^5.7.0"
-
      }
-
    },
    "node_modules/@humanwhocodes/config-array": {
      "version": "0.11.8",
      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
@@ -1224,13 +519,13 @@
      }
    },
    "node_modules/@playwright/test": {
-
      "version": "1.29.2",
-
      "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.29.2.tgz",
-
      "integrity": "sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg==",
+
      "version": "1.30.0",
+
      "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.30.0.tgz",
+
      "integrity": "sha512-SVxkQw1xvn/Wk/EvBnqWIq6NLo1AppwbYOjNLmyU0R1RoQ3rLEBtmjTnElcnz8VEtn11fptj1ECxK0tgURhajw==",
      "dev": true,
      "dependencies": {
        "@types/node": "*",
-
        "playwright-core": "1.29.2"
+
        "playwright-core": "1.30.0"
      },
      "bin": {
        "playwright": "cli.js"
@@ -1271,11 +566,6 @@
        "@sinonjs/commons": "^2.0.0"
      }
    },
-
    "node_modules/@stardazed/streams": {
-
      "version": "3.1.0",
-
      "resolved": "https://registry.npmjs.org/@stardazed/streams/-/streams-3.1.0.tgz",
-
      "integrity": "sha512-+fNbzyPb65oknwBgMjJrfs7dPXIJTDgnrFQcLI9+tpYTvHgrxwlqMm8geV4NA640qp+udIenWQDLU+hsB06Vcw=="
-
    },
    "node_modules/@sveltejs/vite-plugin-svelte": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.0.2.tgz",
@@ -1415,14 +705,14 @@
      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
    },
    "node_modules/@typescript-eslint/eslint-plugin": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.2.tgz",
-
      "integrity": "sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==",
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.49.0.tgz",
+
      "integrity": "sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==",
      "dev": true,
      "dependencies": {
-
        "@typescript-eslint/scope-manager": "5.48.2",
-
        "@typescript-eslint/type-utils": "5.48.2",
-
        "@typescript-eslint/utils": "5.48.2",
+
        "@typescript-eslint/scope-manager": "5.49.0",
+
        "@typescript-eslint/type-utils": "5.49.0",
+
        "@typescript-eslint/utils": "5.49.0",
        "debug": "^4.3.4",
        "ignore": "^5.2.0",
        "natural-compare-lite": "^1.4.0",
@@ -1441,217 +731,23 @@
        "@typescript-eslint/parser": "^5.0.0",
        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
      },
-
      "peerDependenciesMeta": {
-
        "typescript": {
-
          "optional": true
-
        }
-
      }
-
    },
-
    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz",
-
      "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==",
-
      "dev": true,
-
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "@typescript-eslint/visitor-keys": "5.48.2"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      }
-
    },
-
    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz",
-
      "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==",
-
      "dev": true,
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      }
-
    },
-
    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz",
-
      "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==",
-
      "dev": true,
-
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "eslint-visitor-keys": "^3.3.0"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      }
-
    },
-
    "node_modules/@typescript-eslint/parser": {
-
      "version": "5.48.0",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz",
-
      "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==",
-
      "dev": true,
-
      "peer": true,
-
      "dependencies": {
-
        "@typescript-eslint/scope-manager": "5.48.0",
-
        "@typescript-eslint/types": "5.48.0",
-
        "@typescript-eslint/typescript-estree": "5.48.0",
-
        "debug": "^4.3.4"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      },
-
      "peerDependencies": {
-
        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
-
      },
-
      "peerDependenciesMeta": {
-
        "typescript": {
-
          "optional": true
-
        }
-
      }
-
    },
-
    "node_modules/@typescript-eslint/scope-manager": {
-
      "version": "5.48.0",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz",
-
      "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==",
-
      "dev": true,
-
      "peer": true,
-
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.0",
-
        "@typescript-eslint/visitor-keys": "5.48.0"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      }
-
    },
-
    "node_modules/@typescript-eslint/type-utils": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.48.2.tgz",
-
      "integrity": "sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==",
-
      "dev": true,
-
      "dependencies": {
-
        "@typescript-eslint/typescript-estree": "5.48.2",
-
        "@typescript-eslint/utils": "5.48.2",
-
        "debug": "^4.3.4",
-
        "tsutils": "^3.21.0"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      },
-
      "peerDependencies": {
-
        "eslint": "*"
-
      },
-
      "peerDependenciesMeta": {
-
        "typescript": {
-
          "optional": true
-
        }
-
      }
-
    },
-
    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz",
-
      "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==",
-
      "dev": true,
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      }
-
    },
-
    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz",
-
      "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==",
-
      "dev": true,
-
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "@typescript-eslint/visitor-keys": "5.48.2",
-
        "debug": "^4.3.4",
-
        "globby": "^11.1.0",
-
        "is-glob": "^4.0.3",
-
        "semver": "^7.3.7",
-
        "tsutils": "^3.21.0"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      },
-
      "peerDependenciesMeta": {
-
        "typescript": {
-
          "optional": true
-
        }
-
      }
-
    },
-
    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz",
-
      "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==",
-
      "dev": true,
-
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "eslint-visitor-keys": "^3.3.0"
-
      },
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
-
      }
-
    },
-
    "node_modules/@typescript-eslint/types": {
-
      "version": "5.48.0",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz",
-
      "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==",
-
      "dev": true,
-
      "peer": true,
-
      "engines": {
-
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/typescript-eslint"
+
      "peerDependenciesMeta": {
+
        "typescript": {
+
          "optional": true
+
        }
      }
    },
-
    "node_modules/@typescript-eslint/typescript-estree": {
-
      "version": "5.48.0",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz",
-
      "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==",
+
    "node_modules/@typescript-eslint/parser": {
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.49.0.tgz",
+
      "integrity": "sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==",
      "dev": true,
      "peer": true,
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.0",
-
        "@typescript-eslint/visitor-keys": "5.48.0",
-
        "debug": "^4.3.4",
-
        "globby": "^11.1.0",
-
        "is-glob": "^4.0.3",
-
        "semver": "^7.3.7",
-
        "tsutils": "^3.21.0"
+
        "@typescript-eslint/scope-manager": "5.49.0",
+
        "@typescript-eslint/types": "5.49.0",
+
        "@typescript-eslint/typescript-estree": "5.49.0",
+
        "debug": "^4.3.4"
      },
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1660,26 +756,23 @@
        "type": "opencollective",
        "url": "https://opencollective.com/typescript-eslint"
      },
+
      "peerDependencies": {
+
        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+
      },
      "peerDependenciesMeta": {
        "typescript": {
          "optional": true
        }
      }
    },
-
    "node_modules/@typescript-eslint/utils": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.48.2.tgz",
-
      "integrity": "sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==",
+
    "node_modules/@typescript-eslint/scope-manager": {
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.49.0.tgz",
+
      "integrity": "sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==",
      "dev": true,
      "dependencies": {
-
        "@types/json-schema": "^7.0.9",
-
        "@types/semver": "^7.3.12",
-
        "@typescript-eslint/scope-manager": "5.48.2",
-
        "@typescript-eslint/types": "5.48.2",
-
        "@typescript-eslint/typescript-estree": "5.48.2",
-
        "eslint-scope": "^5.1.1",
-
        "eslint-utils": "^3.0.0",
-
        "semver": "^7.3.7"
+
        "@typescript-eslint/types": "5.49.0",
+
        "@typescript-eslint/visitor-keys": "5.49.0"
      },
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1687,19 +780,18 @@
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/typescript-eslint"
-
      },
-
      "peerDependencies": {
-
        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
      }
    },
-
    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.2.tgz",
-
      "integrity": "sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==",
+
    "node_modules/@typescript-eslint/type-utils": {
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.49.0.tgz",
+
      "integrity": "sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==",
      "dev": true,
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "@typescript-eslint/visitor-keys": "5.48.2"
+
        "@typescript-eslint/typescript-estree": "5.49.0",
+
        "@typescript-eslint/utils": "5.49.0",
+
        "debug": "^4.3.4",
+
        "tsutils": "^3.21.0"
      },
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1707,12 +799,20 @@
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/typescript-eslint"
+
      },
+
      "peerDependencies": {
+
        "eslint": "*"
+
      },
+
      "peerDependenciesMeta": {
+
        "typescript": {
+
          "optional": true
+
        }
      }
    },
-
    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.2.tgz",
-
      "integrity": "sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==",
+
    "node_modules/@typescript-eslint/types": {
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.49.0.tgz",
+
      "integrity": "sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==",
      "dev": true,
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1722,14 +822,14 @@
        "url": "https://opencollective.com/typescript-eslint"
      }
    },
-
    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.2.tgz",
-
      "integrity": "sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==",
+
    "node_modules/@typescript-eslint/typescript-estree": {
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.49.0.tgz",
+
      "integrity": "sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==",
      "dev": true,
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "@typescript-eslint/visitor-keys": "5.48.2",
+
        "@typescript-eslint/types": "5.49.0",
+
        "@typescript-eslint/visitor-keys": "5.49.0",
        "debug": "^4.3.4",
        "globby": "^11.1.0",
        "is-glob": "^4.0.3",
@@ -1749,14 +849,20 @@
        }
      }
    },
-
    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
-
      "version": "5.48.2",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.2.tgz",
-
      "integrity": "sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==",
+
    "node_modules/@typescript-eslint/utils": {
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.49.0.tgz",
+
      "integrity": "sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==",
      "dev": true,
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.2",
-
        "eslint-visitor-keys": "^3.3.0"
+
        "@types/json-schema": "^7.0.9",
+
        "@types/semver": "^7.3.12",
+
        "@typescript-eslint/scope-manager": "5.49.0",
+
        "@typescript-eslint/types": "5.49.0",
+
        "@typescript-eslint/typescript-estree": "5.49.0",
+
        "eslint-scope": "^5.1.1",
+
        "eslint-utils": "^3.0.0",
+
        "semver": "^7.3.7"
      },
      "engines": {
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1764,16 +870,18 @@
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/typescript-eslint"
+
      },
+
      "peerDependencies": {
+
        "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
      }
    },
    "node_modules/@typescript-eslint/visitor-keys": {
-
      "version": "5.48.0",
-
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz",
-
      "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==",
+
      "version": "5.49.0",
+
      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.49.0.tgz",
+
      "integrity": "sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==",
      "dev": true,
-
      "peer": true,
      "dependencies": {
-
        "@typescript-eslint/types": "5.48.0",
+
        "@typescript-eslint/types": "5.49.0",
        "eslint-visitor-keys": "^3.3.0"
      },
      "engines": {
@@ -1784,190 +892,75 @@
        "url": "https://opencollective.com/typescript-eslint"
      }
    },
-
    "node_modules/@walletconnect/browser-utils": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/browser-utils/-/browser-utils-1.8.0.tgz",
-
      "integrity": "sha512-Wcqqx+wjxIo9fv6eBUFHPsW1y/bGWWRboni5dfD8PtOmrihrEpOCmvRJe4rfl7xgJW8Ea9UqKEaq0bIRLHlK4A==",
-
      "dependencies": {
-
        "@walletconnect/safe-json": "1.0.0",
-
        "@walletconnect/types": "^1.8.0",
-
        "@walletconnect/window-getters": "1.0.0",
-
        "@walletconnect/window-metadata": "1.0.0",
-
        "detect-browser": "5.2.0"
-
      }
-
    },
-
    "node_modules/@walletconnect/client": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/client/-/client-1.8.0.tgz",
-
      "integrity": "sha512-svyBQ14NHx6Cs2j4TpkQaBI/2AF4+LXz64FojTjMtV4VMMhl81jSO1vNeg+yYhQzvjcGH/GpSwixjyCW0xFBOQ==",
-
      "dependencies": {
-
        "@walletconnect/core": "^1.8.0",
-
        "@walletconnect/iso-crypto": "^1.8.0",
-
        "@walletconnect/types": "^1.8.0",
-
        "@walletconnect/utils": "^1.8.0"
-
      }
-
    },
-
    "node_modules/@walletconnect/core": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-1.8.0.tgz",
-
      "integrity": "sha512-aFTHvEEbXcZ8XdWBw6rpQDte41Rxwnuk3SgTD8/iKGSRTni50gI9S3YEzMj05jozSiOBxQci4pJDMVhIUMtarw==",
-
      "dependencies": {
-
        "@walletconnect/socket-transport": "^1.8.0",
-
        "@walletconnect/types": "^1.8.0",
-
        "@walletconnect/utils": "^1.8.0"
-
      }
-
    },
-
    "node_modules/@walletconnect/crypto": {
-
      "version": "1.0.3",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/crypto/-/crypto-1.0.3.tgz",
-
      "integrity": "sha512-+2jdORD7XQs76I2Odgr3wwrtyuLUXD/kprNVsjWRhhhdO9Mt6WqVzOPu0/t7OHSmgal8k7SoBQzUc5hu/8zL/g==",
-
      "dependencies": {
-
        "@walletconnect/encoding": "^1.0.2",
-
        "@walletconnect/environment": "^1.0.1",
-
        "@walletconnect/randombytes": "^1.0.3",
-
        "aes-js": "^3.1.2",
-
        "hash.js": "^1.1.7",
-
        "tslib": "1.14.1"
-
      }
-
    },
-
    "node_modules/@walletconnect/crypto/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
-
    },
-
    "node_modules/@walletconnect/encoding": {
-
      "version": "1.0.2",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/encoding/-/encoding-1.0.2.tgz",
-
      "integrity": "sha512-CrwSBrjqJ7rpGQcTL3kU+Ief+Bcuu9PH6JLOb+wM6NITX1GTxR/MfNwnQfhLKK6xpRAyj2/nM04OOH6wS8Imag==",
-
      "dependencies": {
-
        "is-typedarray": "1.0.0",
-
        "tslib": "1.14.1",
-
        "typedarray-to-buffer": "3.1.5"
-
      }
-
    },
-
    "node_modules/@walletconnect/encoding/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
-
    },
-
    "node_modules/@walletconnect/environment": {
-
      "version": "1.0.1",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.1.tgz",
-
      "integrity": "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==",
-
      "dependencies": {
-
        "tslib": "1.14.1"
-
      }
-
    },
-
    "node_modules/@walletconnect/environment/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
-
    },
-
    "node_modules/@walletconnect/iso-crypto": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/iso-crypto/-/iso-crypto-1.8.0.tgz",
-
      "integrity": "sha512-pWy19KCyitpfXb70hA73r9FcvklS+FvO9QUIttp3c2mfW8frxgYeRXfxLRCIQTkaYueRKvdqPjbyhPLam508XQ==",
-
      "dependencies": {
-
        "@walletconnect/crypto": "^1.0.2",
-
        "@walletconnect/types": "^1.8.0",
-
        "@walletconnect/utils": "^1.8.0"
-
      }
-
    },
-
    "node_modules/@walletconnect/jsonrpc-types": {
-
      "version": "1.0.2",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.2.tgz",
-
      "integrity": "sha512-CZe8tjJX73OWdHjrBHy7HtAapJ2tT0Q3TYhPBhRxi3643lwPIQWC9En45ldY14TZwgSewkbZ0FtGBZK0G7Bbyg==",
+
    "node_modules/@vitest/expect": {
+
      "version": "0.28.3",
+
      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.28.3.tgz",
+
      "integrity": "sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==",
+
      "dev": true,
      "dependencies": {
-
        "keyvaluestorage-interface": "^1.0.0",
-
        "tslib": "1.14.1"
+
        "@vitest/spy": "0.28.3",
+
        "@vitest/utils": "0.28.3",
+
        "chai": "^4.3.7"
      }
    },
-
    "node_modules/@walletconnect/jsonrpc-types/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
-
    },
-
    "node_modules/@walletconnect/jsonrpc-utils": {
-
      "version": "1.0.4",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.4.tgz",
-
      "integrity": "sha512-y0+tDxcTZ9BHBBKBJbjZxLUXb+zQZCylf7y/jTvDPNx76J0hYYc+F9zHzyqBLeorSKepLTk6yI8hw3NXbAQB3g==",
+
    "node_modules/@vitest/runner": {
+
      "version": "0.28.3",
+
      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-0.28.3.tgz",
+
      "integrity": "sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==",
+
      "dev": true,
      "dependencies": {
-
        "@walletconnect/environment": "^1.0.1",
-
        "@walletconnect/jsonrpc-types": "^1.0.2",
-
        "tslib": "1.14.1"
+
        "@vitest/utils": "0.28.3",
+
        "p-limit": "^4.0.0",
+
        "pathe": "^1.1.0"
      }
    },
-
    "node_modules/@walletconnect/jsonrpc-utils/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
-
    },
-
    "node_modules/@walletconnect/randombytes": {
-
      "version": "1.0.3",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/randombytes/-/randombytes-1.0.3.tgz",
-
      "integrity": "sha512-35lpzxcHFbTN3ABefC9W+uBpNZl1GC4Wpx0ed30gibfO/y9oLdy1NznbV96HARQKSBV9J9M/rrtIvf6a23jfYw==",
+
    "node_modules/@vitest/runner/node_modules/p-limit": {
+
      "version": "4.0.0",
+
      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+
      "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+
      "dev": true,
      "dependencies": {
-
        "@walletconnect/encoding": "^1.0.2",
-
        "@walletconnect/environment": "^1.0.1",
-
        "randombytes": "^2.1.0",
-
        "tslib": "1.14.1"
+
        "yocto-queue": "^1.0.0"
+
      },
+
      "engines": {
+
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+
      },
+
      "funding": {
+
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
-
    "node_modules/@walletconnect/randombytes/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
-
    },
-
    "node_modules/@walletconnect/safe-json": {
+
    "node_modules/@vitest/runner/node_modules/yocto-queue": {
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.0.tgz",
-
      "integrity": "sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg=="
-
    },
-
    "node_modules/@walletconnect/socket-transport": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/socket-transport/-/socket-transport-1.8.0.tgz",
-
      "integrity": "sha512-5DyIyWrzHXTcVp0Vd93zJ5XMW61iDM6bcWT4p8DTRfFsOtW46JquruMhxOLeCOieM4D73kcr3U7WtyR4JUsGuQ==",
-
      "dependencies": {
-
        "@walletconnect/types": "^1.8.0",
-
        "@walletconnect/utils": "^1.8.0",
-
        "ws": "7.5.3"
+
      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
+
      "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=12.20"
+
      },
+
      "funding": {
+
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
-
    "node_modules/@walletconnect/types": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-1.8.0.tgz",
-
      "integrity": "sha512-Cn+3I0V0vT9ghMuzh1KzZvCkiAxTq+1TR2eSqw5E5AVWfmCtECFkVZBP6uUJZ8YjwLqXheI+rnjqPy7sVM4Fyg=="
-
    },
-
    "node_modules/@walletconnect/utils": {
-
      "version": "1.8.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-1.8.0.tgz",
-
      "integrity": "sha512-zExzp8Mj1YiAIBfKNm5u622oNw44WOESzo6hj+Q3apSMIb0Jph9X3GDIdbZmvVZsNPxWDL7uodKgZcCInZv2vA==",
+
    "node_modules/@vitest/spy": {
+
      "version": "0.28.3",
+
      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.28.3.tgz",
+
      "integrity": "sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==",
+
      "dev": true,
      "dependencies": {
-
        "@walletconnect/browser-utils": "^1.8.0",
-
        "@walletconnect/encoding": "^1.0.1",
-
        "@walletconnect/jsonrpc-utils": "^1.0.3",
-
        "@walletconnect/types": "^1.8.0",
-
        "bn.js": "4.11.8",
-
        "js-sha3": "0.8.0",
-
        "query-string": "6.13.5"
+
        "tinyspy": "^1.0.2"
      }
    },
-
    "node_modules/@walletconnect/utils/node_modules/bn.js": {
-
      "version": "4.11.8",
-
      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-
      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
-
    },
-
    "node_modules/@walletconnect/window-getters": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.0.tgz",
-
      "integrity": "sha512-xB0SQsLaleIYIkSsl43vm8EwETpBzJ2gnzk7e0wMF3ktqiTGS6TFHxcprMl5R44KKh4tCcHCJwolMCaDSwtAaA=="
-
    },
-
    "node_modules/@walletconnect/window-metadata": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.0.tgz",
-
      "integrity": "sha512-9eFvmJxIKCC3YWOL97SgRkKhlyGXkrHwamfechmqszbypFspaSk+t2jQXAEU7YClHF6Qjw5eYOmy1//zFi9/GA==",
+
    "node_modules/@vitest/utils": {
+
      "version": "0.28.3",
+
      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.28.3.tgz",
+
      "integrity": "sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==",
+
      "dev": true,
      "dependencies": {
-
        "@walletconnect/window-getters": "^1.0.0"
+
        "cli-truncate": "^3.1.0",
+
        "diff": "^5.1.0",
+
        "loupe": "^2.3.6",
+
        "picocolors": "^1.0.0",
+
        "pretty-format": "^27.5.1"
      }
    },
    "node_modules/@wooorm/starry-night": {
@@ -1986,9 +979,9 @@
      }
    },
    "node_modules/acorn": {
-
      "version": "8.8.1",
-
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
-
      "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+
      "version": "8.8.2",
+
      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+
      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
      "dev": true,
      "bin": {
        "acorn": "bin/acorn"
@@ -2015,11 +1008,6 @@
        "node": ">=0.4.0"
      }
    },
-
    "node_modules/aes-js": {
-
      "version": "3.1.2",
-
      "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz",
-
      "integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ=="
-
    },
    "node_modules/ajv": {
      "version": "6.12.6",
      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -2046,15 +1034,12 @@
      }
    },
    "node_modules/ansi-styles": {
-
      "version": "4.3.0",
-
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-
      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+
      "version": "5.2.0",
+
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+
      "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
      "dev": true,
-
      "dependencies": {
-
        "color-convert": "^2.0.1"
-
      },
      "engines": {
-
        "node": ">=8"
+
        "node": ">=10"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
@@ -2096,17 +1081,6 @@
        "node": "*"
      }
    },
-
    "node_modules/available-typed-arrays": {
-
      "version": "1.0.5",
-
      "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
-
      "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
    "node_modules/balanced-match": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2132,11 +1106,6 @@
        }
      ]
    },
-
    "node_modules/bech32": {
-
      "version": "1.1.4",
-
      "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
-
      "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
-
    },
    "node_modules/binary-extensions": {
      "version": "2.2.0",
      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -2146,11 +1115,6 @@
        "node": ">=8"
      }
    },
-
    "node_modules/bn.js": {
-
      "version": "5.2.1",
-
      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz",
-
      "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ=="
-
    },
    "node_modules/brace-expansion": {
      "version": "1.1.11",
      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2173,11 +1137,6 @@
        "node": ">=8"
      }
    },
-
    "node_modules/brorand": {
-
      "version": "1.1.0",
-
      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-
      "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w=="
-
    },
    "node_modules/buffer": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -2225,18 +1184,6 @@
        "node": ">=8"
      }
    },
-
    "node_modules/call-bind": {
-
      "version": "1.0.2",
-
      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
-
      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
-
      "dependencies": {
-
        "function-bind": "^1.1.1",
-
        "get-intrinsic": "^1.0.2"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
    "node_modules/callsites": {
      "version": "3.1.0",
      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2359,6 +1306,22 @@
        "node": ">= 6"
      }
    },
+
    "node_modules/cli-truncate": {
+
      "version": "3.1.0",
+
      "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
+
      "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
+
      "dev": true,
+
      "dependencies": {
+
        "slice-ansi": "^5.0.0",
+
        "string-width": "^5.0.0"
+
      },
+
      "engines": {
+
        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+
      },
+
      "funding": {
+
        "url": "https://github.com/sponsors/sindresorhus"
+
      }
+
    },
    "node_modules/color-convert": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2445,14 +1408,6 @@
        }
      }
    },
-
    "node_modules/decode-uri-component": {
-
      "version": "0.2.2",
-
      "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
-
      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
-
      "engines": {
-
        "node": ">=0.10"
-
      }
-
    },
    "node_modules/deep-eql": {
      "version": "4.1.3",
      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
@@ -2472,19 +1427,14 @@
      "dev": true
    },
    "node_modules/deepmerge": {
-
      "version": "4.2.2",
-
      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
-
      "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+
      "version": "4.3.0",
+
      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+
      "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
      "dev": true,
      "engines": {
        "node": ">=0.10.0"
      }
    },
-
    "node_modules/detect-browser": {
-
      "version": "5.2.0",
-
      "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.2.0.tgz",
-
      "integrity": "sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA=="
-
    },
    "node_modules/detect-indent": {
      "version": "6.1.0",
      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
@@ -2494,6 +1444,15 @@
        "node": ">=8"
      }
    },
+
    "node_modules/diff": {
+
      "version": "5.1.0",
+
      "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+
      "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=0.3.1"
+
      }
+
    },
    "node_modules/dir-glob": {
      "version": "3.0.1",
      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -2523,24 +1482,17 @@
      "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
      "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ=="
    },
-
    "node_modules/elliptic": {
-
      "version": "6.5.4",
-
      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
-
      "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
-
      "dependencies": {
-
        "bn.js": "^4.11.9",
-
        "brorand": "^1.1.0",
-
        "hash.js": "^1.0.0",
-
        "hmac-drbg": "^1.0.1",
-
        "inherits": "^2.0.4",
-
        "minimalistic-assert": "^1.0.1",
-
        "minimalistic-crypto-utils": "^1.0.1"
-
      }
+
    "node_modules/eastasianwidth": {
+
      "version": "0.2.0",
+
      "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+
      "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+
      "dev": true
    },
-
    "node_modules/elliptic/node_modules/bn.js": {
-
      "version": "4.12.0",
-
      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
-
      "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
+
    "node_modules/emoji-regex": {
+
      "version": "9.2.2",
+
      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+
      "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+
      "dev": true
    },
    "node_modules/es6-promise": {
      "version": "3.3.1",
@@ -2549,9 +1501,9 @@
      "dev": true
    },
    "node_modules/esbuild": {
-
      "version": "0.16.16",
-
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.16.tgz",
-
      "integrity": "sha512-24JyKq10KXM5EBIgPotYIJ2fInNWVVqflv3gicIyQqfmUqi4HvDW1VR790cBgLJHCl96Syy7lhoz7tLFcmuRmg==",
+
      "version": "0.16.17",
+
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.16.17.tgz",
+
      "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==",
      "dev": true,
      "hasInstallScript": true,
      "bin": {
@@ -2561,28 +1513,28 @@
        "node": ">=12"
      },
      "optionalDependencies": {
-
        "@esbuild/android-arm": "0.16.16",
-
        "@esbuild/android-arm64": "0.16.16",
-
        "@esbuild/android-x64": "0.16.16",
-
        "@esbuild/darwin-arm64": "0.16.16",
-
        "@esbuild/darwin-x64": "0.16.16",
-
        "@esbuild/freebsd-arm64": "0.16.16",
-
        "@esbuild/freebsd-x64": "0.16.16",
-
        "@esbuild/linux-arm": "0.16.16",
-
        "@esbuild/linux-arm64": "0.16.16",
-
        "@esbuild/linux-ia32": "0.16.16",
-
        "@esbuild/linux-loong64": "0.16.16",
-
        "@esbuild/linux-mips64el": "0.16.16",
-
        "@esbuild/linux-ppc64": "0.16.16",
-
        "@esbuild/linux-riscv64": "0.16.16",
-
        "@esbuild/linux-s390x": "0.16.16",
-
        "@esbuild/linux-x64": "0.16.16",
-
        "@esbuild/netbsd-x64": "0.16.16",
-
        "@esbuild/openbsd-x64": "0.16.16",
-
        "@esbuild/sunos-x64": "0.16.16",
-
        "@esbuild/win32-arm64": "0.16.16",
-
        "@esbuild/win32-ia32": "0.16.16",
-
        "@esbuild/win32-x64": "0.16.16"
+
        "@esbuild/android-arm": "0.16.17",
+
        "@esbuild/android-arm64": "0.16.17",
+
        "@esbuild/android-x64": "0.16.17",
+
        "@esbuild/darwin-arm64": "0.16.17",
+
        "@esbuild/darwin-x64": "0.16.17",
+
        "@esbuild/freebsd-arm64": "0.16.17",
+
        "@esbuild/freebsd-x64": "0.16.17",
+
        "@esbuild/linux-arm": "0.16.17",
+
        "@esbuild/linux-arm64": "0.16.17",
+
        "@esbuild/linux-ia32": "0.16.17",
+
        "@esbuild/linux-loong64": "0.16.17",
+
        "@esbuild/linux-mips64el": "0.16.17",
+
        "@esbuild/linux-ppc64": "0.16.17",
+
        "@esbuild/linux-riscv64": "0.16.17",
+
        "@esbuild/linux-s390x": "0.16.17",
+
        "@esbuild/linux-x64": "0.16.17",
+
        "@esbuild/netbsd-x64": "0.16.17",
+
        "@esbuild/openbsd-x64": "0.16.17",
+
        "@esbuild/sunos-x64": "0.16.17",
+
        "@esbuild/win32-arm64": "0.16.17",
+
        "@esbuild/win32-ia32": "0.16.17",
+
        "@esbuild/win32-x64": "0.16.17"
      }
    },
    "node_modules/escape-string-regexp": {
@@ -2598,9 +1550,9 @@
      }
    },
    "node_modules/eslint": {
-
      "version": "8.32.0",
-
      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.32.0.tgz",
-
      "integrity": "sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==",
+
      "version": "8.33.0",
+
      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.33.0.tgz",
+
      "integrity": "sha512-WjOpFQgKK8VrCnAtl8We0SUOy/oVZ5NHykyMiagV1M9r8IFpIJX7DduK6n1mpfhlG7T1NLWm2SuD8QB7KFySaA==",
      "dev": true,
      "dependencies": {
        "@eslint/eslintrc": "^1.4.1",
@@ -2712,6 +1664,21 @@
        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
      }
    },
+
    "node_modules/eslint/node_modules/ansi-styles": {
+
      "version": "4.3.0",
+
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+
      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+
      "dev": true,
+
      "dependencies": {
+
        "color-convert": "^2.0.1"
+
      },
+
      "engines": {
+
        "node": ">=8"
+
      },
+
      "funding": {
+
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+
      }
+
    },
    "node_modules/eslint/node_modules/chalk": {
      "version": "4.1.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -2827,61 +1794,6 @@
        "node": ">=0.10.0"
      }
    },
-
    "node_modules/ethers": {
-
      "version": "5.7.2",
-
      "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
-
      "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
-
      "funding": [
-
        {
-
          "type": "individual",
-
          "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
-
        },
-
        {
-
          "type": "individual",
-
          "url": "https://www.buymeacoffee.com/ricmoo"
-
        }
-
      ],
-
      "dependencies": {
-
        "@ethersproject/abi": "5.7.0",
-
        "@ethersproject/abstract-provider": "5.7.0",
-
        "@ethersproject/abstract-signer": "5.7.0",
-
        "@ethersproject/address": "5.7.0",
-
        "@ethersproject/base64": "5.7.0",
-
        "@ethersproject/basex": "5.7.0",
-
        "@ethersproject/bignumber": "5.7.0",
-
        "@ethersproject/bytes": "5.7.0",
-
        "@ethersproject/constants": "5.7.0",
-
        "@ethersproject/contracts": "5.7.0",
-
        "@ethersproject/hash": "5.7.0",
-
        "@ethersproject/hdnode": "5.7.0",
-
        "@ethersproject/json-wallets": "5.7.0",
-
        "@ethersproject/keccak256": "5.7.0",
-
        "@ethersproject/logger": "5.7.0",
-
        "@ethersproject/networks": "5.7.1",
-
        "@ethersproject/pbkdf2": "5.7.0",
-
        "@ethersproject/properties": "5.7.0",
-
        "@ethersproject/providers": "5.7.2",
-
        "@ethersproject/random": "5.7.0",
-
        "@ethersproject/rlp": "5.7.0",
-
        "@ethersproject/sha2": "5.7.0",
-
        "@ethersproject/signing-key": "5.7.0",
-
        "@ethersproject/solidity": "5.7.0",
-
        "@ethersproject/strings": "5.7.0",
-
        "@ethersproject/transactions": "5.7.0",
-
        "@ethersproject/units": "5.7.0",
-
        "@ethersproject/wallet": "5.7.0",
-
        "@ethersproject/web": "5.7.1",
-
        "@ethersproject/wordlists": "5.7.0"
-
      }
-
    },
-
    "node_modules/events": {
-
      "version": "3.3.0",
-
      "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
-
      "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
-
      "engines": {
-
        "node": ">=0.8.x"
-
      }
-
    },
    "node_modules/extend-shallow": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
@@ -3007,14 +1919,6 @@
      "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
      "dev": true
    },
-
    "node_modules/for-each": {
-
      "version": "0.3.3",
-
      "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
-
      "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
-
      "dependencies": {
-
        "is-callable": "^1.1.3"
-
      }
-
    },
    "node_modules/fs-extra": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@@ -3059,7 +1963,8 @@
    "node_modules/function-bind": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+
      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+
      "dev": true
    },
    "node_modules/get-func-name": {
      "version": "2.0.0",
@@ -3070,19 +1975,6 @@
        "node": "*"
      }
    },
-
    "node_modules/get-intrinsic": {
-
      "version": "1.1.3",
-
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
-
      "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
-
      "dependencies": {
-
        "function-bind": "^1.1.1",
-
        "has": "^1.0.3",
-
        "has-symbols": "^1.0.3"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
    "node_modules/glob": {
      "version": "7.2.3",
      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3116,9 +2008,9 @@
      }
    },
    "node_modules/globals": {
-
      "version": "13.19.0",
-
      "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz",
-
      "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==",
+
      "version": "13.20.0",
+
      "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+
      "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
      "dev": true,
      "dependencies": {
        "type-fest": "^0.20.2"
@@ -3150,17 +2042,6 @@
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
-
    "node_modules/gopd": {
-
      "version": "1.0.1",
-
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-
      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
-
      "dependencies": {
-
        "get-intrinsic": "^1.1.3"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
    "node_modules/graceful-fs": {
      "version": "4.2.10",
      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
@@ -3173,9 +2054,9 @@
      "dev": true
    },
    "node_modules/happy-dom": {
-
      "version": "8.1.4",
-
      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.1.4.tgz",
-
      "integrity": "sha512-mUCzXHhSO6fOQlZwKW6z2f/+rYavKNxNrgY4nJ4dp+r8gTGbTENgMZGfM6eJD0DJPRFF8DFyngXdBF93wF96UA==",
+
      "version": "8.2.0",
+
      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.2.0.tgz",
+
      "integrity": "sha512-SBMi/ht8zvtXNuSVpXJu+hOEJtNEbM4CxQukcHMm7FCd1sMuitfESwUMX83gl3C2JcEGLcpx/+JnF+rjGl27+A==",
      "dev": true,
      "dependencies": {
        "css.escape": "^1.5.1",
@@ -3190,81 +2071,29 @@
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+
      "dev": true,
      "dependencies": {
-
        "function-bind": "^1.1.1"
-
      },
-
      "engines": {
-
        "node": ">= 0.4.0"
-
      }
-
    },
-
    "node_modules/has-flag": {
-
      "version": "4.0.0",
-
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-
      "dev": true,
-
      "engines": {
-
        "node": ">=8"
-
      }
-
    },
-
    "node_modules/has-symbols": {
-
      "version": "1.0.3",
-
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-
      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
-
    "node_modules/has-tostringtag": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
-
      "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
-
      "dependencies": {
-
        "has-symbols": "^1.0.2"
-
      },
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
-
    "node_modules/hash.js": {
-
      "version": "1.1.7",
-
      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
-
      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
-
      "dependencies": {
-
        "inherits": "^2.0.3",
-
        "minimalistic-assert": "^1.0.1"
+
        "function-bind": "^1.1.1"
+
      },
+
      "engines": {
+
        "node": ">= 0.4.0"
      }
    },
-
    "node_modules/hast-to-hyperscript": {
-
      "version": "10.0.1",
-
      "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz",
-
      "integrity": "sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw==",
-
      "dependencies": {
-
        "@types/unist": "^2.0.0",
-
        "comma-separated-tokens": "^2.0.0",
-
        "property-information": "^6.0.0",
-
        "space-separated-tokens": "^2.0.0",
-
        "style-to-object": "^0.3.0",
-
        "unist-util-is": "^5.0.0",
-
        "web-namespaces": "^2.0.0"
-
      },
-
      "funding": {
-
        "type": "opencollective",
-
        "url": "https://opencollective.com/unified"
+
    "node_modules/has-flag": {
+
      "version": "4.0.0",
+
      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+
      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=8"
      }
    },
    "node_modules/hast-util-from-parse5": {
-
      "version": "7.1.0",
-
      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz",
-
      "integrity": "sha512-m8yhANIAccpU4K6+121KpPP55sSl9/samzQSQGpb0mTExcNh2WlvjtMwSWFhg6uqD4Rr6Nfa8N6TMypQM51rzQ==",
+
      "version": "7.1.1",
+
      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-7.1.1.tgz",
+
      "integrity": "sha512-R6PoNcUs89ZxLJmMWsVbwSWuz95/9OriyQZ3e2ybwqGsRXzhA6gv49rgGmQvLbZuSNDv9fCg7vV7gXUsvtUFaA==",
      "dependencies": {
        "@types/hast": "^2.0.0",
-
        "@types/parse5": "^6.0.0",
        "@types/unist": "^2.0.0",
        "hastscript": "^7.0.0",
        "property-information": "^6.0.0",
@@ -3347,14 +2176,14 @@
      }
    },
    "node_modules/hast-util-to-parse5": {
-
      "version": "7.0.0",
-
      "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.0.0.tgz",
-
      "integrity": "sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A==",
+
      "version": "7.1.0",
+
      "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz",
+
      "integrity": "sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==",
      "dependencies": {
        "@types/hast": "^2.0.0",
-
        "@types/parse5": "^6.0.0",
-
        "hast-to-hyperscript": "^10.0.0",
+
        "comma-separated-tokens": "^2.0.0",
        "property-information": "^6.0.0",
+
        "space-separated-tokens": "^2.0.0",
        "web-namespaces": "^2.0.0",
        "zwitch": "^2.0.0"
      },
@@ -3397,16 +2226,6 @@
        "he": "bin/he"
      }
    },
-
    "node_modules/hmac-drbg": {
-
      "version": "1.0.1",
-
      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
-
      "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
-
      "dependencies": {
-
        "hash.js": "^1.0.3",
-
        "minimalistic-assert": "^1.0.0",
-
        "minimalistic-crypto-utils": "^1.0.1"
-
      }
-
    },
    "node_modules/html-void-elements": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
@@ -3503,27 +2322,8 @@
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
-
    },
-
    "node_modules/inline-style-parser": {
-
      "version": "0.1.1",
-
      "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz",
-
      "integrity": "sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q=="
-
    },
-
    "node_modules/is-arguments": {
-
      "version": "1.1.1",
-
      "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
-
      "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
-
      "dependencies": {
-
        "call-bind": "^1.0.2",
-
        "has-tostringtag": "^1.0.0"
-
      },
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
+
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+
      "dev": true
    },
    "node_modules/is-binary-path": {
      "version": "2.1.0",
@@ -3542,17 +2342,6 @@
      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
      "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
    },
-
    "node_modules/is-callable": {
-
      "version": "1.2.7",
-
      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
-
      "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
    "node_modules/is-core-module": {
      "version": "2.11.0",
      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
@@ -3582,18 +2371,16 @@
        "node": ">=0.10.0"
      }
    },
-
    "node_modules/is-generator-function": {
-
      "version": "1.0.10",
-
      "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
-
      "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
-
      "dependencies": {
-
        "has-tostringtag": "^1.0.0"
-
      },
+
    "node_modules/is-fullwidth-code-point": {
+
      "version": "4.0.0",
+
      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
+
      "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
+
      "dev": true,
      "engines": {
-
        "node": ">= 0.4"
+
        "node": ">=12"
      },
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
+
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/is-glob": {
@@ -3626,29 +2413,6 @@
        "node": ">=8"
      }
    },
-
    "node_modules/is-typed-array": {
-
      "version": "1.1.10",
-
      "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
-
      "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
-
      "dependencies": {
-
        "available-typed-arrays": "^1.0.5",
-
        "call-bind": "^1.0.2",
-
        "for-each": "^0.3.3",
-
        "gopd": "^1.0.1",
-
        "has-tostringtag": "^1.0.0"
-
      },
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
-
    "node_modules/is-typedarray": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-
      "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
-
    },
    "node_modules/isexe": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3656,20 +2420,15 @@
      "dev": true
    },
    "node_modules/js-sdsl": {
-
      "version": "4.2.0",
-
      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
-
      "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
+
      "version": "4.3.0",
+
      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
+
      "integrity": "sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ==",
      "dev": true,
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/js-sdsl"
      }
    },
-
    "node_modules/js-sha3": {
-
      "version": "0.8.0",
-
      "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
-
      "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
-
    },
    "node_modules/js-yaml": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -3725,11 +2484,6 @@
        "katex": "cli.js"
      }
    },
-
    "node_modules/keyvaluestorage-interface": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz",
-
      "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g=="
-
    },
    "node_modules/kind-of": {
      "version": "6.0.3",
      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -3761,9 +2515,9 @@
      }
    },
    "node_modules/local-pkg": {
-
      "version": "0.4.2",
-
      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz",
-
      "integrity": "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==",
+
      "version": "0.4.3",
+
      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz",
+
      "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
      "dev": true,
      "engines": {
        "node": ">=14"
@@ -3808,11 +2562,15 @@
      }
    },
    "node_modules/lru-cache": {
-
      "version": "7.14.1",
-
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz",
-
      "integrity": "sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA==",
+
      "version": "6.0.0",
+
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+
      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+
      "dev": true,
+
      "dependencies": {
+
        "yallist": "^4.0.0"
+
      },
      "engines": {
-
        "node": ">=12"
+
        "node": ">=10"
      }
    },
    "node_modules/magic-string": {
@@ -3879,16 +2637,6 @@
        "node": ">=4"
      }
    },
-
    "node_modules/minimalistic-assert": {
-
      "version": "1.0.1",
-
      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-
      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
-
    },
-
    "node_modules/minimalistic-crypto-utils": {
-
      "version": "1.0.1",
-
      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-
      "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg=="
-
    },
    "node_modules/minimatch": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3934,12 +2682,6 @@
        "ufo": "^1.0.1"
      }
    },
-
    "node_modules/mlly/node_modules/pathe": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.0.0.tgz",
-
      "integrity": "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==",
-
      "dev": true
-
    },
    "node_modules/mri": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -3980,9 +2722,9 @@
      "dev": true
    },
    "node_modules/node-fetch": {
-
      "version": "2.6.7",
-
      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
-
      "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+
      "version": "2.6.8",
+
      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz",
+
      "integrity": "sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==",
      "dev": true,
      "dependencies": {
        "whatwg-url": "^5.0.0"
@@ -4124,9 +2866,9 @@
      }
    },
    "node_modules/pathe": {
-
      "version": "0.2.0",
-
      "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.2.0.tgz",
-
      "integrity": "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==",
+
      "version": "1.1.0",
+
      "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.0.tgz",
+
      "integrity": "sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==",
      "dev": true
    },
    "node_modules/pathval": {
@@ -4167,12 +2909,6 @@
        "pathe": "^1.0.0"
      }
    },
-
    "node_modules/pkg-types/node_modules/pathe": {
-
      "version": "1.0.0",
-
      "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.0.0.tgz",
-
      "integrity": "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==",
-
      "dev": true
-
    },
    "node_modules/plausible-tracker": {
      "version": "0.3.8",
      "resolved": "https://registry.npmjs.org/plausible-tracker/-/plausible-tracker-0.3.8.tgz",
@@ -4182,9 +2918,9 @@
      }
    },
    "node_modules/playwright-core": {
-
      "version": "1.29.2",
-
      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.2.tgz",
-
      "integrity": "sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA==",
+
      "version": "1.30.0",
+
      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz",
+
      "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==",
      "dev": true,
      "bin": {
        "playwright": "cli.js"
@@ -4251,6 +2987,20 @@
        "svelte": "^3.2.0"
      }
    },
+
    "node_modules/pretty-format": {
+
      "version": "27.5.1",
+
      "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+
      "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "ansi-regex": "^5.0.1",
+
        "ansi-styles": "^5.0.0",
+
        "react-is": "^17.0.1"
+
      },
+
      "engines": {
+
        "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+
      }
+
    },
    "node_modules/property-information": {
      "version": "6.2.0",
      "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.2.0.tgz",
@@ -4261,35 +3011,14 @@
      }
    },
    "node_modules/punycode": {
-
      "version": "2.1.1",
-
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
-
      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+
      "version": "2.3.0",
+
      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+
      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
      "dev": true,
      "engines": {
        "node": ">=6"
      }
    },
-
    "node_modules/pure-svg-code": {
-
      "version": "1.0.6",
-
      "resolved": "https://registry.npmjs.org/pure-svg-code/-/pure-svg-code-1.0.6.tgz",
-
      "integrity": "sha512-uxq2BMTdnKW7jDghpLJrczCd9KDOdyghFtEEpfomqMJkUM83/N+W7sFJPJ3AxBf0mJ3xtxAycl6NW8p6F53jEw=="
-
    },
-
    "node_modules/query-string": {
-
      "version": "6.13.5",
-
      "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.5.tgz",
-
      "integrity": "sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q==",
-
      "dependencies": {
-
        "decode-uri-component": "^0.2.0",
-
        "split-on-first": "^1.0.0",
-
        "strict-uri-encode": "^2.0.0"
-
      },
-
      "engines": {
-
        "node": ">=6"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/sindresorhus"
-
      }
-
    },
    "node_modules/queue-microtask": {
      "version": "1.2.3",
      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4310,13 +3039,11 @@
        }
      ]
    },
-
    "node_modules/randombytes": {
-
      "version": "2.1.0",
-
      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
-
      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
-
      "dependencies": {
-
        "safe-buffer": "^5.1.0"
-
      }
+
    "node_modules/react-is": {
+
      "version": "17.0.2",
+
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+
      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+
      "dev": true
    },
    "node_modules/readdirp": {
      "version": "3.6.0",
@@ -4394,9 +3121,9 @@
      }
    },
    "node_modules/rollup": {
-
      "version": "3.9.1",
-
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.9.1.tgz",
-
      "integrity": "sha512-GswCYHXftN8ZKGVgQhTFUJB/NBXxrRGgO2NCy6E8s1rwEJ4Q9/VttNqcYfEvx4dTo4j58YqdC3OVztPzlKSX8w==",
+
      "version": "3.12.0",
+
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.12.0.tgz",
+
      "integrity": "sha512-4MZ8kA2HNYahIjz63rzrMMRvDqQDeS9LoriJvMuV0V6zIGysP36e9t4yObUfwdT9h/szXoHQideICftcdZklWg==",
      "dev": true,
      "bin": {
        "rollup": "dist/bin/rollup"
@@ -4444,25 +3171,6 @@
        "node": ">=6"
      }
    },
-
    "node_modules/safe-buffer": {
-
      "version": "5.2.1",
-
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
-
      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
-
      "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/safer-buffer": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -4493,11 +3201,6 @@
        "rimraf": "bin.js"
      }
    },
-
    "node_modules/scrypt-js": {
-
      "version": "3.0.1",
-
      "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
-
      "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="
-
    },
    "node_modules/section-matter": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@@ -4525,18 +3228,6 @@
        "node": ">=10"
      }
    },
-
    "node_modules/semver/node_modules/lru-cache": {
-
      "version": "6.0.0",
-
      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-
      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-
      "dev": true,
-
      "dependencies": {
-
        "yallist": "^4.0.0"
-
      },
-
      "engines": {
-
        "node": ">=10"
-
      }
-
    },
    "node_modules/shebang-command": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -4573,19 +3264,47 @@
        "node": ">=8"
      }
    },
+
    "node_modules/slice-ansi": {
+
      "version": "5.0.0",
+
      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
+
      "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
+
      "dev": true,
+
      "dependencies": {
+
        "ansi-styles": "^6.0.0",
+
        "is-fullwidth-code-point": "^4.0.0"
+
      },
+
      "engines": {
+
        "node": ">=12"
+
      },
+
      "funding": {
+
        "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+
      }
+
    },
+
    "node_modules/slice-ansi/node_modules/ansi-styles": {
+
      "version": "6.2.1",
+
      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
+
      "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=12"
+
      },
+
      "funding": {
+
        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+
      }
+
    },
    "node_modules/sorcery": {
-
      "version": "0.10.0",
-
      "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz",
-
      "integrity": "sha512-R5ocFmKZQFfSTstfOtHjJuAwbpGyf9qjQa1egyhvXSbM7emjrtLXtGdZsDJDABC85YBfVvrOiGWKSYXPKdvP1g==",
+
      "version": "0.11.0",
+
      "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
+
      "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==",
      "dev": true,
      "dependencies": {
+
        "@jridgewell/sourcemap-codec": "^1.4.14",
        "buffer-crc32": "^0.2.5",
        "minimist": "^1.2.0",
-
        "sander": "^0.5.0",
-
        "sourcemap-codec": "^1.3.0"
+
        "sander": "^0.5.0"
      },
      "bin": {
-
        "sorcery": "bin/index.js"
+
        "sorcery": "bin/sorcery"
      }
    },
    "node_modules/source-map": {
@@ -4616,13 +3335,6 @@
        "source-map": "^0.6.0"
      }
    },
-
    "node_modules/sourcemap-codec": {
-
      "version": "1.4.8",
-
      "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
-
      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
-
      "deprecated": "Please use @jridgewell/sourcemap-codec instead",
-
      "dev": true
-
    },
    "node_modules/space-separated-tokens": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
@@ -4632,26 +3344,60 @@
        "url": "https://github.com/sponsors/wooorm"
      }
    },
-
    "node_modules/split-on-first": {
-
      "version": "1.1.0",
-
      "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
-
      "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
-
      "engines": {
-
        "node": ">=6"
-
      }
-
    },
    "node_modules/stackback": {
      "version": "0.0.2",
      "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
      "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
      "dev": true
    },
-
    "node_modules/strict-uri-encode": {
-
      "version": "2.0.0",
-
      "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
-
      "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
+
    "node_modules/std-env": {
+
      "version": "3.3.1",
+
      "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.1.tgz",
+
      "integrity": "sha512-3H20QlwQsSm2OvAxWIYhs+j01MzzqwMwGiiO1NQaJYZgJZFPuAbf95/DiKRBSTYIJ2FeGUc+B/6mPGcWP9dO3Q==",
+
      "dev": true
+
    },
+
    "node_modules/string-width": {
+
      "version": "5.1.2",
+
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+
      "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+
      "dev": true,
+
      "dependencies": {
+
        "eastasianwidth": "^0.2.0",
+
        "emoji-regex": "^9.2.2",
+
        "strip-ansi": "^7.0.1"
+
      },
      "engines": {
-
        "node": ">=4"
+
        "node": ">=12"
+
      },
+
      "funding": {
+
        "url": "https://github.com/sponsors/sindresorhus"
+
      }
+
    },
+
    "node_modules/string-width/node_modules/ansi-regex": {
+
      "version": "6.0.1",
+
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
+
      "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
+
      "dev": true,
+
      "engines": {
+
        "node": ">=12"
+
      },
+
      "funding": {
+
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+
      }
+
    },
+
    "node_modules/string-width/node_modules/strip-ansi": {
+
      "version": "7.0.1",
+
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz",
+
      "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
+
      "dev": true,
+
      "dependencies": {
+
        "ansi-regex": "^6.0.1"
+
      },
+
      "engines": {
+
        "node": ">=12"
+
      },
+
      "funding": {
+
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
      }
    },
    "node_modules/stringify-entities": {
@@ -4723,14 +3469,6 @@
        "url": "https://github.com/sponsors/antfu"
      }
    },
-
    "node_modules/style-to-object": {
-
      "version": "0.3.0",
-
      "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
-
      "integrity": "sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==",
-
      "dependencies": {
-
        "inline-style-parser": "0.1.1"
-
      }
-
    },
    "node_modules/supports-color": {
      "version": "7.2.0",
      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -4764,9 +3502,9 @@
      }
    },
    "node_modules/svelte-check": {
-
      "version": "3.0.2",
-
      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.0.2.tgz",
-
      "integrity": "sha512-DkhKhV0Jt0gh7q9DBB26+J2Vfb9y4/4JWxnbkXBZha7542LOhwvj3edJFjyJ+xjdaXyInZ+YRRYc3V6wytP2ew==",
+
      "version": "3.0.3",
+
      "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.0.3.tgz",
+
      "integrity": "sha512-ByBFXo3bfHRGIsYEasHkdMhLkNleVfszX/Ns1oip58tPJlKdo5Ssr8kgVIuo5oq00hss8AIcdesuy0Xt0BcTvg==",
      "dev": true,
      "dependencies": {
        "@jridgewell/trace-mapping": "^0.3.17",
@@ -4798,9 +3536,9 @@
      }
    },
    "node_modules/svelte-preprocess": {
-
      "version": "5.0.0",
-
      "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.0.tgz",
-
      "integrity": "sha512-q7lpa7i2FBu8Pa+G0MmuQQWETBwCKgsGmuq1Sf6n8q4uaG9ZLcLP0Y+etC6bF4sE6EbLxfiI38zV6RfPe3RSfg==",
+
      "version": "5.0.1",
+
      "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.1.tgz",
+
      "integrity": "sha512-0HXyhCoc9rsW4zGOgtInylC6qj259E1hpFnJMJWTf+aIfeqh4O/QHT31KT2hvPEqQfdjmqBR/kO2JDkkciBLrQ==",
      "dev": true,
      "hasInstallScript": true,
      "dependencies": {
@@ -4808,7 +3546,7 @@
        "@types/sass": "^1.43.1",
        "detect-indent": "^6.1.0",
        "magic-string": "^0.27.0",
-
        "sorcery": "^0.10.0",
+
        "sorcery": "^0.11.0",
        "strip-indent": "^3.0.0"
      },
      "engines": {
@@ -4873,9 +3611,9 @@
      "dev": true
    },
    "node_modules/tinypool": {
-
      "version": "0.3.0",
-
      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.0.tgz",
-
      "integrity": "sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==",
+
      "version": "0.3.1",
+
      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.3.1.tgz",
+
      "integrity": "sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==",
      "dev": true,
      "engines": {
        "node": ">=14.0.0"
@@ -4909,9 +3647,9 @@
      "dev": true
    },
    "node_modules/tslib": {
-
      "version": "2.4.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
-
      "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==",
+
      "version": "1.14.1",
+
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
      "dev": true
    },
    "node_modules/tsutils": {
@@ -4929,12 +3667,6 @@
        "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
      }
    },
-
    "node_modules/tsutils/node_modules/tslib": {
-
      "version": "1.14.1",
-
      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
-
      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
-
      "dev": true
-
    },
    "node_modules/twemoji": {
      "version": "14.0.2",
      "resolved": "https://registry.npmjs.org/twemoji/-/twemoji-14.0.2.tgz",
@@ -4984,14 +3716,6 @@
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
-
    "node_modules/typedarray-to-buffer": {
-
      "version": "3.1.5",
-
      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
-
      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
-
      "dependencies": {
-
        "is-typedarray": "^1.0.0"
-
      }
-
    },
    "node_modules/typescript": {
      "version": "4.9.4",
      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
@@ -5012,18 +3736,18 @@
      "dev": true
    },
    "node_modules/unist-util-is": {
-
      "version": "5.1.1",
-
      "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz",
-
      "integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==",
+
      "version": "5.2.0",
+
      "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.0.tgz",
+
      "integrity": "sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ==",
      "funding": {
        "type": "opencollective",
        "url": "https://opencollective.com/unified"
      }
    },
    "node_modules/unist-util-position": {
-
      "version": "4.0.3",
-
      "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz",
-
      "integrity": "sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==",
+
      "version": "4.0.4",
+
      "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.4.tgz",
+
      "integrity": "sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==",
      "dependencies": {
        "@types/unist": "^2.0.0"
      },
@@ -5033,9 +3757,9 @@
      }
    },
    "node_modules/unist-util-stringify-position": {
-
      "version": "3.0.2",
-
      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz",
-
      "integrity": "sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==",
+
      "version": "3.0.3",
+
      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz",
+
      "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==",
      "dependencies": {
        "@types/unist": "^2.0.0"
      },
@@ -5045,9 +3769,9 @@
      }
    },
    "node_modules/unist-util-visit": {
-
      "version": "4.1.1",
-
      "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.1.tgz",
-
      "integrity": "sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==",
+
      "version": "4.1.2",
+
      "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz",
+
      "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==",
      "dependencies": {
        "@types/unist": "^2.0.0",
        "unist-util-is": "^5.0.0",
@@ -5059,9 +3783,9 @@
      }
    },
    "node_modules/unist-util-visit-parents": {
-
      "version": "5.1.1",
-
      "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.1.tgz",
-
      "integrity": "sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==",
+
      "version": "5.1.3",
+
      "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz",
+
      "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==",
      "dependencies": {
        "@types/unist": "^2.0.0",
        "unist-util-is": "^5.0.0"
@@ -5088,18 +3812,6 @@
        "punycode": "^2.1.0"
      }
    },
-
    "node_modules/util": {
-
      "version": "0.12.5",
-
      "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
-
      "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
-
      "dependencies": {
-
        "inherits": "^2.0.3",
-
        "is-arguments": "^1.0.4",
-
        "is-generator-function": "^1.0.7",
-
        "is-typed-array": "^1.1.3",
-
        "which-typed-array": "^1.1.2"
-
      }
-
    },
    "node_modules/vfile": {
      "version": "5.3.6",
      "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.6.tgz",
@@ -5213,15 +3925,15 @@
      }
    },
    "node_modules/vite-node": {
-
      "version": "0.27.1",
-
      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.27.1.tgz",
-
      "integrity": "sha512-d6+ue/3NzsfndWaPbYh/bFkHbmAWfDXI4B874zRx+WREnG6CUHUbBC8lKaRYZjeR6gCPN5m1aVNNRXBYICA9XA==",
+
      "version": "0.28.3",
+
      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-0.28.3.tgz",
+
      "integrity": "sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==",
      "dev": true,
      "dependencies": {
        "cac": "^6.7.14",
        "debug": "^4.3.4",
        "mlly": "^1.1.0",
-
        "pathe": "^0.2.0",
+
        "pathe": "^1.1.0",
        "picocolors": "^1.0.0",
        "source-map": "^0.6.1",
        "source-map-support": "^0.5.21",
@@ -5252,28 +3964,34 @@
      }
    },
    "node_modules/vitest": {
-
      "version": "0.27.1",
-
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.27.1.tgz",
-
      "integrity": "sha512-1sIpQ1DVFTEn7c1ici1XHcVfdU4nKiBmPtPAtGKJJJLuJjojTv/OHGgcf69P57alM4ty8V4NMv+7Yoi5Cxqx9g==",
+
      "version": "0.28.3",
+
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.28.3.tgz",
+
      "integrity": "sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==",
      "dev": true,
      "dependencies": {
        "@types/chai": "^4.3.4",
        "@types/chai-subset": "^1.3.3",
        "@types/node": "*",
+
        "@vitest/expect": "0.28.3",
+
        "@vitest/runner": "0.28.3",
+
        "@vitest/spy": "0.28.3",
+
        "@vitest/utils": "0.28.3",
        "acorn": "^8.8.1",
        "acorn-walk": "^8.2.0",
        "cac": "^6.7.14",
        "chai": "^4.3.7",
        "debug": "^4.3.4",
        "local-pkg": "^0.4.2",
+
        "pathe": "^1.1.0",
        "picocolors": "^1.0.0",
        "source-map": "^0.6.1",
+
        "std-env": "^3.3.1",
        "strip-literal": "^1.0.0",
        "tinybench": "^2.3.1",
-
        "tinypool": "^0.3.0",
+
        "tinypool": "^0.3.1",
        "tinyspy": "^1.0.2",
        "vite": "^3.0.0 || ^4.0.0",
-
        "vite-node": "0.27.1",
+
        "vite-node": "0.28.3",
        "why-is-node-running": "^2.2.2"
      },
      "bin": {
@@ -5390,25 +4108,6 @@
        "node": ">= 8"
      }
    },
-
    "node_modules/which-typed-array": {
-
      "version": "1.1.9",
-
      "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
-
      "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
-
      "dependencies": {
-
        "available-typed-arrays": "^1.0.5",
-
        "call-bind": "^1.0.2",
-
        "for-each": "^0.3.3",
-
        "gopd": "^1.0.1",
-
        "has-tostringtag": "^1.0.0",
-
        "is-typed-array": "^1.1.10"
-
      },
-
      "engines": {
-
        "node": ">= 0.4"
-
      },
-
      "funding": {
-
        "url": "https://github.com/sponsors/ljharb"
-
      }
-
    },
    "node_modules/why-is-node-running": {
      "version": "2.2.2",
      "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
@@ -5440,26 +4139,6 @@
      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
      "dev": true
    },
-
    "node_modules/ws": {
-
      "version": "7.5.3",
-
      "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
-
      "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
-
      "engines": {
-
        "node": ">=8.3.0"
-
      },
-
      "peerDependencies": {
-
        "bufferutil": "^4.0.1",
-
        "utf-8-validate": "^5.0.2"
-
      },
-
      "peerDependenciesMeta": {
-
        "bufferutil": {
-
          "optional": true
-
        },
-
        "utf-8-validate": {
-
          "optional": true
-
        }
-
      }
-
    },
    "node_modules/yallist": {
      "version": "4.0.0",
      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
modified package.json
@@ -17,7 +17,7 @@
    "node": ">=18.12.1"
  },
  "devDependencies": {
-
    "@playwright/test": "^1.29.2",
+
    "@playwright/test": "^1.30.0",
    "@sinonjs/fake-timers": "^10.0.2",
    "@sveltejs/vite-plugin-svelte": "^2.0.2",
    "@tsconfig/svelte": "^3.0.0",
@@ -28,40 +28,31 @@
    "@types/md5": "^2.3.2",
    "@types/node": "^18.11.18",
    "@types/sinonjs__fake-timers": "^8.1.2",
-
    "@typescript-eslint/eslint-plugin": "^5.48.2",
+
    "@typescript-eslint/eslint-plugin": "^5.49.0",
    "chalk": "^5.2.0",
-
    "eslint": "^8.32.0",
+
    "eslint": "^8.33.0",
    "eslint-plugin-svelte3": "^4.0.0",
-
    "happy-dom": "^8.1.4",
+
    "happy-dom": "^8.2.0",
    "prettier": "^2.8.3",
    "prettier-plugin-svelte": "^2.9.0",
-
    "svelte-check": "^3.0.2",
-
    "tslib": "^2.4.1",
+
    "svelte-check": "^3.0.3",
    "typescript": "^4.9.4",
    "vite": "^4.0.4",
-
    "vitest": "^0.27.1"
+
    "vitest": "^0.28.3"
  },
  "dependencies": {
-
    "@ethersproject/abstract-provider": "^5.7.0",
    "@radicle/gray-matter": "4.1.0",
-
    "@stardazed/streams": "^3.1.0",
-
    "@walletconnect/client": "^1.8.0",
    "@wooorm/starry-night": "^1.5.0",
    "buffer": "^6.0.3",
    "dompurify": "^2.4.3",
-
    "ethers": "^5.7.2",
-
    "events": "^3.3.0",
    "hast-util-to-dom": "^3.1.1",
    "hast-util-to-html": "^8.0.4",
    "katex": "^0.16.4",
    "lodash": "^4.17.21",
-
    "lru-cache": "^7.14.1",
    "marked": "^4.2.12",
    "md5": "^2.3.0",
    "plausible-tracker": "^0.3.8",
-
    "pure-svg-code": "^1.0.6",
    "svelte": "^3.55.1",
-
    "twemoji": "^14.0.2",
-
    "util": "^0.12.5"
+
    "twemoji": "^14.0.2"
  }
}
modified src/App.svelte
@@ -1,8 +1,6 @@
<script lang="ts">
  import Plausible from "plausible-tracker";

-
  import { Connection, state, session } from "@app/lib/session";
-
  import { getWallet } from "@app/lib/wallet";
  import { initialize, activeRouteStore } from "@app/lib/router";
  import { unreachable } from "@app/lib/utils";

@@ -10,17 +8,10 @@
  import ModalPortal from "./App/ModalPortal.svelte";
  import Hotkeys from "./App/Hotkeys.svelte";

-
  import Loading from "@app/components/Loading.svelte";
  import NotFound from "@app/components/NotFound.svelte";
-
  import Error from "@app/components/Error.svelte";
-

-
  import Faucet from "@app/views/faucet/Faucet.svelte";
  import Home from "@app/views/home/Index.svelte";
-
  import Profile from "@app/views/profiles/Profile.svelte";
  import Projects from "@app/views/projects/View.svelte";
-
  import Registrations from "@app/views/registrations/Routes.svelte";
  import Seeds from "@app/views/seeds/Routes.svelte";
-
  import Vesting from "@app/views/vesting/Routes.svelte";

  initialize();

@@ -32,21 +23,6 @@

    plausible.enableAutoPageviews();
  }
-

-
  const loadWallet = getWallet().then(async wallet => {
-
    if ($state.connection === Connection.Connected) {
-
      state.refreshBalance(wallet);
-
    } else if ($state.connection === Connection.Disconnected) {
-
      // Update the session state if we're already connected to WalletConnect
-
      // from a previous session.
-
      if (wallet.walletConnect.client.connected) {
-
        await state.connectWalletConnect(wallet);
-
      } else if (wallet.metamask.connected) {
-
        await state.connectMetamask(wallet);
-
      }
-
    }
-
    return wallet;
-
  });
</script>

<style>
@@ -74,42 +50,20 @@
<Hotkeys />

<div class="app">
-
  {#await loadWallet}
-
    <div class="wrapper">
-
      <Loading center />
-
    </div>
-
  {:then wallet}
-
    <Header session={$session} {wallet} />
-
    <div class="wrapper">
-
      {#if $activeRouteStore.resource === "home"}
-
        <Home />
-
      {:else if $activeRouteStore.resource === "faucet"}
-
        <Faucet {wallet} />
-
      {:else if $activeRouteStore.resource === "seeds"}
-
        <Seeds host={$activeRouteStore.params.host} />
-
      {:else if $activeRouteStore.resource === "registrations"}
-
        <Registrations {wallet} activeRoute={$activeRouteStore} />
-
      {:else if $activeRouteStore.resource === "vesting"}
-
        <Vesting {wallet} activeRoute={$activeRouteStore} />
-
      {:else if $activeRouteStore.resource === "projects"}
-
        <Projects {wallet} activeRoute={$activeRouteStore} />
-
      {:else if $activeRouteStore.resource === "profile"}
-
        <Profile
-
          addressOrName={$activeRouteStore.params.addressOrName}
-
          {wallet} />
-
      {:else if $activeRouteStore.resource === "404"}
-
        <div class="wrapper" style:justify-content="center">
-
          <NotFound title="404" subtitle="Nothing here" />
-
        </div>
-
      {:else}
-
        {unreachable($activeRouteStore)}
-
      {/if}
-
    </div>
-
  {:catch err}
-
    <div class="wrapper" style:justify-content="center">
-
      <Error
-
        title="Error connecting to network"
-
        message={err.message ? err.message : JSON.stringify(err)} />
-
    </div>
-
  {/await}
+
  <Header />
+
  <div class="wrapper">
+
    {#if $activeRouteStore.resource === "home"}
+
      <Home />
+
    {:else if $activeRouteStore.resource === "seeds"}
+
      <Seeds host={$activeRouteStore.params.host} />
+
    {:else if $activeRouteStore.resource === "projects"}
+
      <Projects activeRoute={$activeRouteStore} />
+
    {:else if $activeRouteStore.resource === "404"}
+
      <div class="layout-centered">
+
        <NotFound title="404" subtitle="Nothing here" />
+
      </div>
+
    {:else}
+
      {unreachable($activeRouteStore)}
+
    {/if}
+
  </div>
</div>
modified src/App/Header.svelte
@@ -1,31 +1,11 @@
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-
  import type { Session } from "@app/lib/session";
-

-
  import Avatar from "@app/components/Avatar.svelte";
-
  import Button from "@app/components/Button.svelte";
-
  import Connect from "@app/components/Connect.svelte";
  import Floating from "@app/components/Floating.svelte";
  import Icon from "@app/components/Icon.svelte";
  import Link from "@app/components/Link.svelte";
-
  import Loading from "@app/components/Loading.svelte";
  import SettingsDropdown from "./Header/SettingsDropdown.svelte";

  import Logo from "./Header/Logo.svelte";
  import Search from "./Header/Search.svelte";
-

-
  import { Profile, ProfileType } from "@app/lib/profile";
-
  import { closeFocused } from "@app/components/Floating.svelte";
-
  import { disconnectWallet } from "@app/lib/session";
-
  import { formatAddress, formatBalance } from "@app/lib/utils";
-

-
  export let session: Session | null;
-
  export let wallet: Wallet;
-

-
  let sessionButtonHover = false;
-

-
  $: address = session && session.address;
-
  $: tokenBalance = session && session.tokenBalance;
</script>

<style>
@@ -53,72 +33,15 @@
  .search {
    width: 16rem;
  }
-
  .connect {
-
    display: inline-block;
-
  }
-
  .network {
-
    color: var(--color-tertiary-6);
-
    background-color: var(--color-tertiary-1);
-
    line-height: 1.5em;
-
    padding: 0rem 1rem;
-
    height: var(--button-regular-height);
-
    display: flex;
-
    align-items: center;
-
    border-radius: var(--border-radius-round);
-
  }
-
  .network:hover {
-
    background-color: var(--color-tertiary-3);
-
  }
-
  .network.unavailable {
-
    color: var(--color-foreground-5);
-
    background-color: var(--color-foreground-3);
-
  }
-
  .network:last-child {
-
    margin-right: 0;
-
  }
-
  .register {
-
    display: inline-block;
-
    padding: 0.5rem 0.5rem;
-
    cursor: pointer;
-
    user-select: none;
-
    color: var(--color-foreground);
-
  }
-
  .register:hover {
-
    color: var(--color-foreground);
-
  }
-
  .balance {
-
    white-space: nowrap;
-
  }

  @media (max-width: 720px) {
    header .right {
      gap: 1rem;
    }
-
    .network,
-
    .search,
-
    .register,
-
    .balance {
+
    .search {
      display: none;
    }
  }
-
  .modal {
-
    background: var(--color-background);
-
    border-radius: var(--border-radius);
-
    box-shadow: var(--elevation-low);
-
    max-width: 22.5rem;
-
    min-width: 18rem;
-
    padding: 1.5rem;
-
    position: absolute;
-
    right: 1.5rem;
-
    top: 5rem;
-
  }
-
  .modal-register {
-
    color: var(--color-foreground);
-
    padding-left: 0.5rem;
-
  }
-
  .modal-register:hover {
-
    color: var(--color-foreground);
-
  }

  .toggle {
    width: 2.5rem;
@@ -141,67 +64,11 @@
  <div class="left">
    <Link route={{ resource: "home" }}><span class="logo"><Logo /></span></Link>
    <div class="search">
-
      <Search {wallet} />
+
      <Search />
    </div>
  </div>

  <div class="right">
-
    {#if wallet && wallet.network.name === "goerli"}
-
      <Link
-
        route={{
-
          resource: "faucet",
-
        }}>
-
        <span class="network">Goerli</span>
-
      </Link>
-
    {:else if wallet && wallet.network.name === "homestead"}
-
      <!-- Don't show anything -->
-
    {:else}
-
      <span class="network unavailable">No Network</span>
-
    {/if}
-
    <Link
-
      route={{
-
        resource: "registrations",
-
        params: { view: { resource: "form" } },
-
      }}>
-
      <span class="register">Register</span>
-
    </Link>
-

-
    {#if address}
-
      <span class="balance">
-
        {#if tokenBalance}
-
          {formatBalance(tokenBalance)}
-
          <span class="txt-bold">RAD</span>
-
        {:else}
-
          <Loading small />
-
        {/if}
-
      </span>
-

-
      <Button
-
        style="width: 10rem; white-space: nowrap;"
-
        variant="foreground"
-
        on:click={() => disconnectWallet(wallet)}
-
        on:mouseover={() => (sessionButtonHover = true)}
-
        on:focus={() => (sessionButtonHover = true)}
-
        on:mouseout={() => (sessionButtonHover = false)}
-
        on:blur={() => (sessionButtonHover = false)}>
-
        {#await Profile.get(address, ProfileType.Minimal, wallet)}
-
          <Loading small center />
-
        {:then profile}
-
          {#if sessionButtonHover}
-
            Disconnect
-
          {:else}
-
            <Avatar
-
              source={profile.avatar ?? address}
-
              title={address}
-
              inline />{formatAddress(address)}
-
          {/if}
-
        {/await}
-
      </Button>
-
    {:else if wallet}
-
      <span class="connect">
-
        <Connect buttonVariant="foreground" {wallet} />
-
      </span>
-
    {/if}
    <Floating>
      <div slot="toggle">
        <button class="toggle" name="Settings">
@@ -217,28 +84,6 @@
            <Icon name="ellipsis" />
          </span>
        </div>
-

-
        <svelte:fragment slot="modal">
-
          <div class="modal">
-
            <div style="padding-bottom: 1rem;">
-
              <Search
-
                {wallet}
-
                on:finished={() => {
-
                  closeFocused();
-
                }} />
-
            </div>
-
            <Link
-
              route={{
-
                resource: "registrations",
-
                params: { view: { resource: "form" } },
-
              }}
-
              on:click={() => {
-
                closeFocused();
-
              }}>
-
              <span class="modal-register">Register</span>
-
            </Link>
-
          </div>
-
        </svelte:fragment>
      </Floating>
    </div>
  </div>
modified src/App/Header/Search.svelte
@@ -1,6 +1,4 @@
<script lang="ts" strictEvents>
-
  import type { Wallet } from "@app/lib/wallet";
-

  import debounce from "lodash/debounce";
  import { createEventDispatcher } from "svelte";
  import * as router from "@app/lib/router";
@@ -11,8 +9,6 @@
  import TextInput from "@app/components/TextInput.svelte";
  import { unreachable } from "@app/lib/utils";

-
  export let wallet: Wallet;
-

  const dispatch = createEventDispatcher<{
    finished: never;
  }>();
@@ -37,46 +33,36 @@
    loading = true;

    const query = input;
-
    const searchResult = await Search.searchProjectsAndProfiles(input, wallet);
+
    const searchResult = await Search.searchProjectsAndProfiles(input);

    if (searchResult.type === "nothing") {
      shake();
    } else if (searchResult.type === "error") {
      // TODO: show some kind of notification to the user.
      shake();
-
    } else if (searchResult.type === "singleProfile") {
-
      input = "";
-
      router.push({
-
        resource: "profile",
-
        params: { addressOrName: searchResult.id },
-
      });
-
      dispatch("finished");
-
    } else if (searchResult.type === "singleProject") {
-
      input = "";
-
      router.push({
-
        resource: "projects",
-
        params: {
-
          view: { resource: "tree" },
-
          id: searchResult.id,
-
          peer: undefined,
-
          profile: undefined,
-
          seed: searchResult.seedHost,
-
          hash: undefined,
-
          search: undefined,
-
        },
-
      });
-
      dispatch("finished");
-
    } else if (searchResult.type === "projectsAndProfiles") {
-
      // TODO: show some kind of notification about any errors to the user.
+
    } else if (searchResult.type === "projects") {
      input = "";
-
      modal.show({
-
        component: SearchResultsModal,
-
        props: {
-
          wallet,
-
          results: searchResult.projectsAndProfiles,
-
          query,
-
        },
-
      });
+
      if (searchResult.projects.length === 1) {
+
        router.push({
+
          resource: "projects",
+
          params: {
+
            view: { resource: "tree" },
+
            id: searchResult.projects[0].info.id,
+
            peer: undefined,
+
            seed: searchResult.projects[0].seed.host,
+
            hash: undefined,
+
            search: undefined,
+
          },
+
        });
+
      } else {
+
        modal.show({
+
          component: SearchResultsModal,
+
          props: {
+
            results: searchResult.projects,
+
            query,
+
          },
+
        });
+
      }
      dispatch("finished");
    } else {
      unreachable(searchResult);
modified src/App/Header/SearchResultsModal.svelte
@@ -1,16 +1,13 @@
<script lang="ts" strictEvents>
-
  import type { Wallet } from "@app/lib/wallet";
-
  import type { ProjectsAndProfiles } from "@app/lib/search";
+
  import type { ProjectResult } from "@app/lib/search";

-
  import Address from "@app/components/Address.svelte";
+
  import * as modal from "@app/lib/modal";
  import Link from "@app/components/Link.svelte";
  import Modal from "@app/components/Modal.svelte";
  import { formatRadicleId, getSeedEmoji } from "@app/lib/utils";
-
  import * as modal from "@app/lib/modal";

  export let query: string;
-
  export let results: ProjectsAndProfiles;
-
  export let wallet: Wallet;
+
  export let results: ProjectResult[];
</script>

<style>
@@ -31,10 +28,10 @@

<Modal emoji="🔍" title={`Results for "${query}"`}>
  <span slot="body" class="results">
-
    {#if results.projects.length > 0}
+
    {#if results.length > 0}
      <div class="txt-highlight txt-medium">Projects</div>
      <ul>
-
        {#each results.projects as project}
+
        {#each results as project}
          <li>
            <Link
              on:click={modal.hide}
@@ -59,15 +56,5 @@
        {/each}
      </ul>
    {/if}
-
    {#if results.profiles.length > 0}
-
      <div class="txt-highlight txt-medium">ENS names</div>
-
      <ul>
-
        {#each results.profiles as profile}
-
          <li>
-
            <Address address={profile.address} {profile} {wallet} resolve />
-
          </li>
-
        {/each}
-
      </ul>
-
    {/if}
  </span>
</Modal>
deleted src/components/Address.svelte
@@ -1,106 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { onMount } from "svelte";
-
  import { ethers } from "ethers";
-

-
  import Avatar from "@app/components/Avatar.svelte";
-
  import Badge from "@app/components/Badge.svelte";
-
  import Link from "@app/components/Link.svelte";
-
  import {
-
    AddressType,
-
    formatAddress,
-
    identifyAddress,
-
    parseEnsLabel,
-
  } from "@app/lib/utils";
-
  import { Profile, ProfileType } from "@app/lib/profile";
-

-
  export let address: string;
-
  export let wallet: Wallet;
-
  export let resolve = false;
-
  export let noBadge = false;
-
  export let noAvatar = false;
-
  export let compact = false;
-
  export let small = false;
-
  export let tiny = false;
-
  export let highlight = false;
-
  // This property allows components eg. Header.svelte to pass a resolved profile object.
-
  export let profile: Profile | null = null;
-

-
  let addressType: AddressType | null = null;
-

-
  const addressOrName = profile?.ens?.name || address;
-

-
  onMount(async () => {
-
    if (!profile) {
-
      identifyAddress(address, wallet).then(
-
        (t: AddressType) => (addressType = t),
-
      );
-

-
      if (resolve) {
-
        Profile.get(address, ProfileType.Minimal, wallet).then(
-
          p => (profile = p),
-
        );
-
      }
-
    } else {
-
      // If there is a profile we can use the profile.type to avoid identifying it again.
-
      addressType = profile.type;
-
    }
-
  });
-
  $: addressLabel =
-
    resolve && profile?.name
-
      ? compact
-
        ? parseEnsLabel(profile.name, wallet)
-
        : profile.name
-
      : checksumAddress;
-
  $: checksumAddress = compact
-
    ? formatAddress(address)
-
    : ethers.utils.getAddress(address);
-
</script>
-

-
<style>
-
  .address {
-
    display: inline-flex;
-
    align-items: center;
-
    height: 100%;
-
  }
-
  .highlight {
-
    color: var(--color-foreground-6);
-
    font-weight: var(--font-weight-bold);
-
  }
-
  .wrapper {
-
    gap: 0.5rem;
-
    display: flex;
-
    align-items: center;
-
  }
-
</style>
-

-
<div
-
  class="address"
-
  title={address}
-
  class:txt-small={small}
-
  class:txt-tiny={tiny}
-
  class:highlight>
-
  {#if !noAvatar}
-
    {#if resolve && profile?.avatar}
-
      <Avatar inline source={profile.avatar} title={address} />
-
    {:else}
-
      <Avatar inline source={address} title={address} />
-
    {/if}
-
  {/if}
-
  <div class="wrapper">
-
    <Link route={{ resource: "profile", params: { addressOrName } }}>
-
      {addressLabel}
-
    </Link>
-

-
    {#if !noBadge}
-
      {#if addressType === AddressType.Org}
-
        <Badge variant="foreground">org</Badge>
-
      {:else if addressType === AddressType.Vesting}
-
        <Badge variant="foreground">vesting contract</Badge>
-
      {:else if addressType === AddressType.Contract}
-
        <Badge variant="foreground">contract</Badge>
-
      {/if}
-
    {/if}
-
  </div>
-
</div>
deleted src/components/Async.svelte
@@ -1,35 +0,0 @@
-
<script lang="ts">
-
  import Loading from "@app/components/Loading.svelte";
-

-
  type T = $$Generic;
-

-
  export let fetch: Promise<T>;
-
</script>
-

-
<style>
-
  .error {
-
    color: var(--color-negative);
-
    background-color: var(--color-negative-2);
-
    word-wrap: break-word;
-
    text-overflow: ellipsis;
-
    overflow-x: hidden;
-
    padding: 1rem;
-
  }
-
  .error::selection,
-
  .error ::selection {
-
    background-color: var(--color-negative);
-
  }
-
</style>
-

-
{#await fetch}
-
  <Loading center />
-
{:then result}
-
  <slot {result} />
-
{:catch err}
-
  <div class="error txt-tiny">
-
    <div>
-
      API request to <span class="txt-monospace">{err.url}</span>
-
      failed.
-
    </div>
-
  </div>
-
{/await}
modified src/components/Authorship.svelte
@@ -1,37 +1,15 @@
<script lang="ts">
  import type { Author } from "@app/lib/cobs";
-
  import type { Wallet } from "@app/lib/wallet";

-
  import { onMount } from "svelte";
-

-
  import Address from "@app/components/Address.svelte";
-
  import { Profile, ProfileType } from "@app/lib/profile";
  import {
    formatRadicleId,
    formatSeedId,
    formatTimestamp,
  } from "@app/lib/utils";

-
  export let noAvatar = false;
  export let author: Author;
  export let timestamp: number;
  export let caption: string;
-
  export let wallet: Wallet;
-
  export let profile: Profile | null = null;
-

-
  onMount(async () => {
-
    if (author.profile?.ens?.name) {
-
      try {
-
        profile = await Profile.get(
-
          author.profile.ens.name,
-
          ProfileType.Minimal,
-
          wallet,
-
        );
-
      } catch {
-
        // Ignore profile not found.
-
      }
-
    }
-
  });
</script>

<style>
@@ -54,26 +32,9 @@
</style>

<span class="authorship txt-tiny">
-
  {#if profile}
-
    <Address
-
      tiny
-
      highlight
-
      resolve
-
      noBadge
-
      compact
-
      {noAvatar}
-
      {wallet}
-
      {profile}
-
      address={profile.address} />
-
  {:else if author.profile}
-
    <span class="highlight">
-
      {author.profile.name}
-
    </span>
-
  {:else}
-
    <span class="highlight">
-
      {window.HEARTWOOD ? formatSeedId(author.id) : formatRadicleId(author.id)}
-
    </span>
-
  {/if}
+
  <span class="highlight">
+
    {window.HEARTWOOD ? formatSeedId(author.id) : formatRadicleId(author.id)}
+
  </span>
  <span class="caption">&nbsp;{caption}&nbsp;</span>
  <span class="txt-tiny date">
    {formatTimestamp(timestamp)}
modified src/components/Avatar.svelte
@@ -1,6 +1,6 @@
<script lang="ts">
  import { createIcon } from "@app/lib/blockies";
-
  import { isAddress, isPeerId, isRadicleId } from "@app/lib/utils";
+
  import { isPeerId, isRadicleId } from "@app/lib/utils";

  export let title: string;
  export let source: string;
@@ -22,7 +22,7 @@
    return avatar.toDataURL();
  }

-
  if (isAddress(source) || isRadicleId(source) || isPeerId(source)) {
+
  if (isRadicleId(source) || isPeerId(source)) {
    source = createContainer(source);
  }
  grayscale = isPeerId(title) || isRadicleId(title);
modified src/components/Comment.svelte
@@ -1,9 +1,6 @@
<script lang="ts">
  import type { Blob } from "@app/lib/project";
  import type { Comment, Thread } from "@app/lib/issue";
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { onMount } from "svelte";

  import Authorship from "@app/components/Authorship.svelte";
  import Avatar from "@app/components/Avatar.svelte";
@@ -11,34 +8,19 @@
  import ReactionSelector from "./Comment/ReactionSelector.svelte";
  import Reactions from "./Comment/Reactions.svelte";

-
  import { Profile, ProfileType } from "@app/lib/profile";
-

  export let comment: Comment | Thread;
-
  export let wallet: Wallet;
  export let caption = "left a comment";
  export let getImage: (path: string) => Promise<Blob>;

-
  let profile: Profile | null = null;
-

-
  onMount(async () => {
-
    if (comment.author.profile?.ens?.name) {
-
      profile = await Profile.get(
-
        comment.author.profile.ens.name,
-
        ProfileType.Minimal,
-
        wallet,
-
      );
-
    }
-
  });
-

  const templateComment = `<!--
Please enter a comment message for your patch update. Leaving this
blank is also okay.
-->`;

-
  $: source = profile?.avatar || comment.author.id;
-
  $: title =
-
    profile?.name ||
-
    (comment.author.profile ? comment.author.profile.name : comment.author.id);
+
  $: source = comment.author.id;
+
  $: title = comment.author.profile
+
    ? comment.author.profile.name
+
    : comment.author.id;

  const selectReaction = (event: { detail: string }) => {
    // TODO: Once we allow adding reactions through the http-api, we should call it here.
@@ -89,10 +71,7 @@ blank is also okay.
  <div class="card">
    <div class="card-header">
      <Authorship
-
        noAvatar
-
        {wallet}
        {caption}
-
        {profile}
        author={comment.author}
        timestamp={comment.timestamp} />
      <ReactionSelector on:select={selectReaction} />
deleted src/components/Connect.svelte
@@ -1,75 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { get } from "svelte/store";
-

-
  import * as modal from "@app/lib/modal";
-
  import Button from "@app/components/Button.svelte";
-
  import ConnectWalletModal from "@app/components/Connect/ConnectWalletModal.svelte";
-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-

-
  import { Connection, state } from "@app/lib/session";
-

-
  export let caption = "Connect";
-
  export let wallet: Wallet;
-
  export let buttonVariant: "foreground" | "primary";
-
  export let autofocus: boolean = false;
-

-
  const onModalClose = () => {
-
    const wcs = get(wallet.walletConnect.state);
-

-
    if (wcs.state === "open") {
-
      wallet.walletConnect.state.set({ state: "close" });
-
      wcs.onClose();
-
    }
-
  };
-

-
  const onConnect = async () => {
-
    try {
-
      await state.connectWalletConnect(wallet);
-
    } catch (error: any) {
-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Connection failed",
-
          error: error.message,
-
        },
-
      });
-
    }
-
  };
-
  const modalStore = modal.modalStore;
-

-
  $: connecting = $state.connection === Connection.Connecting;
-
  $: walletConnectState = wallet.walletConnect.state;
-
  $: if ($walletConnectState.state === "open") {
-
    modal.show({
-
      component: ConnectWalletModal,
-
      props: {
-
        wallet,
-
        uri: $walletConnectState.uri,
-
        onClose: () => {
-
          onModalClose();
-
          modal.hide();
-
        },
-
      },
-
      hideCallback: onModalClose,
-
    });
-
  } else {
-
    if ($modalStore?.component === ConnectWalletModal) {
-
      modal.hide();
-
    }
-
  }
-
</script>
-

-
<Button
-
  {autofocus}
-
  on:click={onConnect}
-
  variant={buttonVariant}
-
  disabled={connecting}
-
  waiting={connecting}>
-
  {#if connecting}
-
    Connecting…
-
  {:else}
-
    {caption}
-
  {/if}
-
</Button>
deleted src/components/Connect/ConnectWalletModal.svelte
@@ -1,72 +0,0 @@
-
<script lang="ts" strictEvents>
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { qrcode } from "pure-svg-code";
-

-
  import Modal from "@app/components/Modal.svelte";
-
  import { state } from "@app/lib/session";
-

-
  export let uri: string;
-
  export let wallet: Wallet;
-
  export let onClose: () => void;
-

-
  $: svgString = qrcode({
-
    content: uri,
-
    width: 200,
-
    height: 200,
-
    color: "black",
-
    padding: 0,
-
    background: "white",
-
    ecl: "M",
-
  });
-
</script>
-

-
<style>
-
  .qrcode-wrapper {
-
    width: min-content;
-
    margin: 0 auto;
-
    padding: 0.75rem;
-
    background-color: white;
-
    border-radius: var(--border-radius-small);
-
  }
-
  .qrcode {
-
    width: 200px;
-
    height: 200px;
-
  }
-
</style>
-

-
<Modal
-
  emoji="👛"
-
  title="Connect your wallet"
-
  primaryAction={{
-
    name: "Connect with Metamask",
-
    callback: () => {
-
      state.connectMetamask(wallet);
-
    },
-
    props: {
-
      variant: "secondary",
-
      size: "small",
-
      disabled: !wallet.metamask.signer,
-
    },
-
  }}
-
  closeAction={{
-
    callback: onClose,
-
    props: { size: "small" },
-
  }}>
-
  <div slot="subtitle">
-
    <div class="txt-small">
-
      Scan the QR code with <span class="txt-bold">WalletConnect</span>
-
      or use
-
      <span class="txt-bold">Metamask</span>
-
      .
-
    </div>
-
  </div>
-

-
  <div slot="body" style:margin="1rem auto">
-
    <div class="qrcode-wrapper">
-
      <div class="qrcode">
-
        {@html svgString}
-
      </div>
-
    </div>
-
  </div>
-
</Modal>
deleted src/components/Form.svelte
@@ -1,286 +0,0 @@
-
<script context="module" lang="ts">
-
  export interface RegistrationRecord {
-
    name: string;
-
    value: string;
-
  }
-

-
  export interface Field {
-
    name: string;
-
    value: string;
-
    label?: string;
-
    validate?: string;
-
    placeholder?: string;
-
    description: string;
-
    resolve?: boolean;
-
    editable: boolean;
-
    error?: string | null;
-
    example?: string;
-
    url?: string;
-
  }
-

-
  const validationExamples: Record<string, string> = {
-
    URL: "https://acme.xyz/",
-
    ID: "eip155:1:0xd1bb21bd5a432d2919c82bcefe1bc7f8cc9207d9",
-
    handle: "acme",
-
    id: "hydkkcf6k9be5fuszdhpqbctu3q3fuwagj874wx2puia8ti8coygh1",
-
    identity: window.HEARTWOOD
-
      ? "rad:zKtT7DmF9H34KkvcKj9PHW19WzjT"
-
      : "rad:git:hnrkqdpm9ub19oc8dccx44echy75hzfsezyio",
-
    domain: "seed.acme.xyz",
-
    address: "0x17a8c096733BD5F87aD43D7A2A4d1C42ab8A2A70",
-
  };
-

-
  const validationTypes: { [index: string]: RegExp } = {
-
    URL: /^(https:\/\/|http:\/\/|ipfs:\/\/)\S+/,
-
    ID: /^z[a-z]+:[a-zA-Z0-9:-]{1,64}$/,
-
    // Github
-
    //   Username may only contain alphanumeric characters or hyphens.
-
    //   Username cannot have multiple consecutive hyphens.
-
    //   Username cannot begin or end with a hyphen.
-
    //   Maximum is 39 characters.
-
    // Twitter
-
    //   Username may only contain alphanumeric characters or underscores.
-
    //   Maximum is 15 characters.
-
    // For simplification of the regex pattern we use a combined version of both requirements.
-
    handle: /^[a-zA-Z0-9-_]{1,39}$/,
-
    address: /^0x[a-zA-Z0-9]{40}$/,
-
    id: /^[a-z0-9]+$/,
-
    identity: window.HEARTWOOD
-
      ? /^rad:[a-z0-9]{28}$/
-
      : /^rad:git:[a-z0-9]{37}$/,
-
    domain: /^[^/:$!_;,@#]+\.[a-z]{2,}$/,
-
  };
-
</script>
-

-
<script lang="ts" strictEvents>
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import cloneDeep from "lodash/cloneDeep";
-
  import { createEventDispatcher } from "svelte";
-
  import { marked } from "marked";
-
  import {
-
    markdownExtensions as extensions,
-
    capitalize,
-
    isUrl,
-
    isAddress,
-
    formatSeedId,
-
  } from "@app/lib/utils";
-

-
  import Address from "@app/components/Address.svelte";
-
  import Button from "@app/components/Button.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";
-

-
  export let fields: Field[];
-
  export let editable = false;
-
  export let disabled = false;
-
  export let wallet: Wallet;
-

-
  let formFields = cloneDeep(fields);
-
  let hasErrors = false;
-

-
  marked.use({ extensions });
-

-
  const check = (event: Event): void => {
-
    const name = (<HTMLInputElement>event.target).name;
-
    const value = (<HTMLInputElement>event.target).value;
-

-
    formFields = formFields.map(field => {
-
      if (field.name === name && field.validate) {
-
        hasErrors =
-
          value.length > 0
-
            ? !validationTypes[field.validate].test(value)
-
            : false;
-
        return {
-
          ...field,
-
          value: value,
-
          error: hasErrors ? `Must be a valid ${field.validate}` : undefined,
-
          example: validationExamples[field.validate],
-
        };
-
      }
-
      return field;
-
    });
-
  };
-

-
  const cleanup = (fields: Field[]): RegistrationRecord[] => {
-
    return fields
-
      .filter(field => field.editable)
-
      .map(field => {
-
        return {
-
          name: field.name,
-
          value: field.value ? field.value.trim() : "",
-
        };
-
      });
-
  };
-
  const dispatch = createEventDispatcher<{
-
    save: RegistrationRecord[];
-
    validate: never;
-
    cancel: never;
-
  }>();
-
  const save = () => dispatch("save", cleanup(formFields));
-
  function validate(event: Event) {
-
    check(event);
-
    dispatch("validate");
-
  }
-
  const cancel = () => {
-
    formFields = cloneDeep(fields);
-
    dispatch("cancel");
-
  };
-
</script>
-

-
<style>
-
  .fields {
-
    display: grid;
-
    grid-template-columns: 6rem auto;
-
    gap: 1rem 1.5rem;
-
  }
-
  .fields > div {
-
    place-self: center start;
-
  }
-

-
  .field {
-
    display: flex;
-
    align-items: flex-start;
-
    width: 28rem;
-
    height: 2.5rem;
-
    border: 1px dashed transparent;
-
    padding: 0.25rem 1rem;
-
    margin: 0;
-
    white-space: nowrap;
-
  }
-
  .ellipsis {
-
    width: 28rem;
-
    overflow: hidden;
-
    text-overflow: ellipsis;
-
  }
-

-
  .description {
-
    padding-left: 1rem;
-
    max-width: 32rem;
-
  }
-
  .description :global(p) {
-
    padding: 0;
-
    margin: 0;
-
  }
-

-
  .description.invalid {
-
    color: var(--color-negative);
-
  }
-

-
  .label {
-
    border: 1px solid transparent;
-
    padding: 0.25rem;
-
    height: 100%;
-
    display: block;
-
  }
-

-
  .actions {
-
    margin-top: 2rem;
-
    text-align: center;
-
    visibility: hidden;
-
    gap: 1.5rem;
-
    display: flex;
-
    justify-content: center;
-
  }
-
  .actions.editable {
-
    visibility: visible;
-
  }
-
  .text-input {
-
    width: 28rem;
-
  }
-
  @media (max-width: 720px) {
-
    .field {
-
      width: unset;
-
    }
-
    .text-input {
-
      width: 14rem;
-
    }
-
  }
-
</style>
-

-
<div class="fields">
-
  {#each formFields as field}
-
    <div class="label txt-highlight">
-
      {field.label || capitalize(field.name)}
-
    </div>
-
    <div>
-
      {#if field.editable && editable}
-
        <div class="text-input">
-
          <TextInput
-
            variant="dashed"
-
            name={field.name}
-
            placeholder={field.placeholder}
-
            on:change={validate}
-
            on:input={() => (field.error = null)}
-
            bind:value={field.value}
-
            {disabled} />
-
        </div>
-
      {:else}
-
        <span class="field">
-
          {#if field.value}
-
            {#if isUrl(field.value)}
-
              <span class="ellipsis">
-
                <a
-
                  href={field.value}
-
                  class="txt-link"
-
                  target="_blank"
-
                  rel="noreferrer">
-
                  {field.value}
-
                </a>
-
              </span>
-
            {:else if isAddress(field.value)}
-
              <div class="layout-desktop-inline">
-
                <Address
-
                  resolve={field.resolve ?? false}
-
                  address={field.value}
-
                  {wallet} />
-
              </div>
-
              <div class="layout-mobile-inline">
-
                <Address
-
                  compact
-
                  resolve={field.resolve ?? false}
-
                  address={field.value}
-
                  {wallet} />
-
              </div>
-
            {:else if field.url}
-
              <div>
-
                <a href={field.url} class="txt-link">{field.value}</a>
-
              </div>
-
            {:else if field.validate === "id"}
-
              <div class="layout-mobile">
-
                {formatSeedId(field.value)}
-
              </div>
-
              <div class="layout-desktop">
-
                {field.value}
-
              </div>
-
            {:else}
-
              {field.value}
-
            {/if}
-
          {:else}
-
            <span class="txt-missing">&cross; Not set</span>
-
          {/if}
-
        </span>
-
      {/if}
-
      {#if field.error}
-
        <div class="description txt-faded txt-small invalid">
-
          {#if field.example}
-
            {field.error}, eg.
-
            <em>{field.example}</em>
-
          {:else}
-
            {field.error}
-
          {/if}
-
        </div>
-
      {:else}
-
        <div class="description txt-faded txt-small">
-
          {@html marked(field.description)}
-
        </div>
-
      {/if}
-
    </div>
-
  {/each}
-
</div>
-

-
<div class="actions" class:editable>
-
  <Button on:click={cancel} {disabled} variant="foreground">Cancel</Button>
-
  <Button on:click={save} disabled={hasErrors || disabled} variant="primary">
-
    Save
-
  </Button>
-
</div>
modified src/components/Icon.svelte
@@ -7,16 +7,12 @@
    | "clipboard-small"
    | "ellipsis"
    | "fork"
-
    | "github"
    | "moon"
    | "checkmark"
    | "sun"
-
    | "twitter"
    | "gear"
    | "chevron-down"
-
    | "chevron-up"
-
    | "etherscan"
-
    | "url";
+
    | "chevron-up";
</script>

<style>
@@ -60,42 +56,6 @@
    14.7859 16.9725C14.8779 16.9315 14.9607 16.8724 15.0293 16.7987L19.2793
    12.5487C19.4198 12.4081 19.4987 12.2175 19.4987 12.0187C19.4987 11.82
    19.4198 11.6293 19.2793 11.4887L15.0293 7.23871V7.23871Z" />
-
  {:else if name === "etherscan"}
-
    <path
-
      fill-rule="evenodd"
-
      clip-rule="evenodd"
-
      d="m12.001 4.8414c-3.9536 0-7.1586 3.205-7.1586 7.1586 0 1.4947
-
    0.45765 2.8814 1.2405 4.029 0.14846 0.0072 0.29643 0.01103 0.44383
-
    0.01179v-4.8829c0-0.23253 0.18853-0.42109 0.42109-0.42109h1.6844c0.23256 0
-
    0.42109 0.18857 0.42109 0.42109v4.6036c0.28408-0.06325 0.56493-0.13618
-
    0.84219-0.21771v-6.0703c0-0.23256 0.18853-0.42109
-
    0.42109-0.42109h1.6844c0.23253 0 0.42109 0.18853 0.42109
-
    0.42109v5.066c0.28752-0.14351 0.56839-0.29359
-
    0.84219-0.44855v-6.3019c0-0.23256 0.18857-0.42109
-
    0.4211-0.42109h1.6844c0.23253 0 0.42109 0.18853 0.42109
-
    0.42109v4.6166c1.5372-1.1835 2.6045-2.3046
-
    2.9851-2.7241-0.96346-2.8161-3.6334-4.8402-6.775-4.8402zm7.3852
-
    5.4209c-0.47314 0.51981-1.8267 1.9326-3.7716 3.3228-0.27337
-
    0.19539-0.666-0.0066-0.666-0.34252v-5.0324h-0.84219v6.1245c0 0.14982-0.07967
-
    0.28837-0.20912 0.36383-0.54498 0.3175-1.1208 0.61909-1.723 0.8912-0.27388
-
    0.12372-0.5945-0.08329-0.5945-0.38379v-5.3114h-0.84219v5.9606c0
-
    0.18334-0.11866 0.34563-0.29334 0.4013-0.56266 0.17913-1.1429 0.32668-1.7377
-
    0.43356-0.24549
-
    0.04413-0.49555-0.16507-0.49555-0.41444v-4.6966h-0.84219v4.877c0
-
    0.22714-0.18015 0.41335-0.40718 0.42084-0.32874
-
    0.01086-0.58124-0.01095-0.90874-0.01668-0.42767
-
    0-0.63194-0.28744-0.85625-0.64916-0.75855-1.2233-1.1966-2.6665-1.1966-4.2109
-
    0-4.4187 3.5821-8.0008 8.0008-8.0008 3.4905 0 6.458 2.2349 7.5514 5.351
-
    0.13374 0.38113 0.09955 0.62011-0.16616 0.91215zm0.31068 0.92928c0.24558
-
    0.07765 0.29216 0.3165 0.29932 0.54043 0.14132 4.4581-3.5249 8.2688-7.9952
-
    8.2688-1.6352
-
    0-3.157-0.491-4.4245-1.3338-0.15447-0.10266-0.22354-0.29434-0.17006-0.47205
-
    0.053471-0.17762 0.2169-0.29931 0.4024-0.29965 3.1395-0.0061 5.924-1.539
-
    7.9792-3.1492 1.8644-1.46063.0905-2.9551 3.3867-3.3309 0.13046-0.16541
-
    0.29544-0.29544 0.52216-0.22369zm-10.399 7.4391c0.83386 0.34033 1.7464
-
    0.52788 2.7033 0.52788 3.689 0 6.7262-2.7903 7.1162-6.3755-0.62086
-
    0.68883-1.5821 1.6643-2.8098 2.626-1.8078 1.4163-4.2267 2.8293-7.0097
-
    3.2216z" />
  {:else if name === "clipboard"}
    <path
      d="M9 5H14.7071L18 8.29289V17H9V5ZM10 6V16H17V9H14V6H10ZM15
@@ -233,43 +193,6 @@
    10.6875 18.6562C10.6875 19.4329 11.3171 20.0625 12.0938 20.0625C12.8704
    20.0625 13.5 19.4329 13.5 18.6562C13.5 17.8796 12.8704 17.25 12.0938
    17.25Z" />
-
  {:else if name === "github"}
-
    <path
-
      clip-rule="evenodd"
-
      fill-rule="evenodd"
-
      d="m7.3295 4.5202c-0.26315-0.090455-0.55523 0.13567-0.52819
-
  0.41659l0.22799 3.0462c-0.34577 0.3331-1.0298 1.2253-1.0298 2.873 0 1.7391
-
  0.87721 2.9043 2.1775 3.6113 0.30568 0.16625 0.63459 0.3073 0.98108
-
  0.42571l-0.49322
-
  0.9844-1.2994-0.10729c-0.4524-0.037363-0.87865-0.22746-1.2087-0.53908l-1.0828-1.0224c-0.16064-0.15169-0.41382-0.14441-0.5655
-
  0.01616-0.15168 0.16065-0.14442 0.41387 0.016209 0.56548l1.0828 1.0225c0.46207
-
  0.43628 1.0588 0.70246 1.6922 0.7547l0.97925 0.08089c-0.18373 0.37987-0.27927
-
  0.79662-0.27927 1.219v0.63245c0 0.2209-0.1791 0.40003-0.40003
-
  0.40003h-0.40003c-0.22093 0-0.40003 0.17905-0.40003 0.40003 0 0.2209 0.1791
-
  0.40003 0.40003 0.40003h0.40003c0.6628 0 1.2001-0.53732
-
  1.2001-1.2001v-0.63245c0-0.31106 0.072566-0.61789
-
  0.21192-0.89599l0.92927-1.8546c0.40628 0.09553 0.82889 0.16585 1.2603
-
  0.21426l-0.3999 4.7353c-0.01859 0.2201 0.1448 0.41363 0.36495 0.43228 0.22016
-
  0.01856 0.4137-0.14481 0.43226-0.36499l0.40003-4.7369 8e-5 -9.6e-4c0.53228
-
  0.02768 1.0706 0.02768 1.6028 0l8e-5 9.6e-4 0.40003 4.7369c0.01856 0.22018
-
  0.2121 0.38355 0.43228 0.36499 0.2201-0.01864 0.38355-0.21218
-
  0.36491-0.43228l-0.39987-4.7353c0.39443-0.04424 0.7815-0.10681
-
  1.1555-0.19026l0.99776 1.8113c0.16289 0.29554 0.24826 0.62757 0.24826
-
  0.96504v0.58277c0 0.66277 0.53732 1.2001 1.2001 1.2001h0.40003c0.2209 0
-
  0.40003-0.17914 0.40003-0.40003
-
  0-0.22098-0.17913-0.40003-0.40003-0.40003h-0.40003c-0.2209
-
  0-0.40003-0.17913-0.40003-0.40003v-0.58277c0-0.47244-0.11953-0.93728-0.34755-1.3511l-0.90431-1.6416c0.38083-0.12465
-
  0.74142-0.27586 1.0745-0.45692 1.3003-0.70702 2.1775-1.8722 2.1775-3.6113
-
  0-1.6477-0.68398-2.5399-1.0298-2.873l0.22794-3.0454c0.027042-0.27529-0.26266-0.50894-0.52844-0.41737l-3.0828
-
  0.98573c-0.74062-0.18142-1.5924-0.27064-2.3875-0.27064-0.79502 0-1.6469
-
  0.089215-2.3874 0.27064zm0.5121 3.5965c0.012201 0.12941-0.037451
-
  0.25373-0.13439 0.3386-0.1738 0.1528-0.32207 0.31325-0.44444 0.51351-0.22285
-
  0.36466-0.46318 0.96791-0.46318 1.8872 0 3.1088 3.5475 3.7601 6.0005 3.7601
-
  2.4529 0 6.0005-0.65125 6.0005-3.7601
-
  0-1.7223-0.81855-2.3326-0.87087-2.372-0.11889-0.082175-0.18474-0.22163-0.17121-0.36685l0.1989-2.6568-2.6353
-
  0.8426c-0.07233 0.023138-0.14977 0.025194-0.2233
-
  0.00593-0.68854-0.1805-1.5179-0.27343-2.2987-0.27343s-1.6102 0.092928-2.2987
-
  0.27343c-0.07348 0.019266-0.15092 0.017209-0.22327-0.00593l-2.6353-0.8426z" />
  {:else if name === "moon"}
    <path
      fill-rule="evenodd"
@@ -292,27 +215,6 @@
    22.5 9H21V10.5C21 10.9142 20.6642 11.25 20.25 11.25C19.8358 11.25 19.5
    10.9142 19.5 10.5V9H18C17.5858 9 17.25 8.66421 17.25 8.25C17.25 7.83579
    17.5858 7.5 18 7.5H19.5V6C19.5 5.58579 19.8358 5.25 20.25 5.25Z" />
-
  {:else if name === "url"}
-
    <path
-
      clip-rule="evenodd"
-
      fill-rule="evenodd"
-
      d="m12.671 11.04c-0.67865-0.74752-1.0922-1.7401-1.0922-2.8293 0-2.3254
-
  1.8851-4.2105 4.2105-4.2105 2.3254 0 4.2105 1.8851 4.2105 4.2105 0
-
  2.3254-1.8851 4.2105-4.2105 4.2105-0.93263
-
  0-1.7944-0.30324-2.4921-0.81642l-1.6927 1.6927c0.51318 0.69777 0.81642 1.5595
-
  0.81642 2.4921 0 2.3254-1.8851 4.2105-4.2105 4.2105-2.3254
-
  0-4.2105-1.8851-4.2105-4.2105 0-2.3254 1.8851-4.2105 4.2105-4.2105 1.0892 0
-
  2.0818 0.41356 2.8293 1.0922zm-0.2501-2.8293c0-1.8603 1.5081-3.3684
-
  3.3684-3.3684 1.8603 0 3.3684 1.5081 3.3684 3.3684 0 1.8603-1.5081
-
  3.3684-3.3684 3.3684-0.69962
-
  0-1.3495-0.2133-1.888-0.57844l1.2291-1.2291c0.16446-0.16443 0.16446-0.43102
-
  0-0.59545-0.16438-0.16444-0.43099-0.16444-0.59545 0l-1.2675
-
  1.2676c-0.52674-0.59443-0.84648-1.3764-0.84648-2.233zm-3.2451 6.3245
-
  1.2676-1.2675c-0.59443-0.52674-1.3764-0.84648-2.233-0.84648-1.8603 0-3.3684
-
  1.5081-3.3684 3.3684 0 1.8603 1.5081 3.3684 3.3684 3.3684 1.8603 0
-
  3.3684-1.5081 3.3684-3.3684 0-0.69962-0.2133-1.3495-0.57844-1.888l-1.2291
-
  1.2291c-0.16443 0.16446-0.43102 0.16446-0.59545
-
  0-0.16444-0.16438-0.16444-0.43099 0-0.59545z" />
  {:else if name === "checkmark"}
    <path
      fill-rule="evenodd"
modified src/config.json
@@ -1,5 +1,4 @@
{
-
  "walletConnect": { "bridge": "https://radicle.bridge.walletconnect.org" },
  "reactions": ["👍", "👎", "😄", "🎉", "🙁", "🚀", "👀"],
  "seeds": {
    "pinned": [
modified src/global.d.ts
@@ -22,12 +22,6 @@ declare global {
    e2eTestStubs: {
      FakeTimers: FakeTimers;
    };
-

-
    // Used in
-
    //   src/session.ts
-
    //   src/wallet.ts
-
    ethereum: any;
-
    registrarState: any;
  }
}

deleted src/lib/cache.ts
@@ -1,25 +0,0 @@
-
import LruCache from "lru-cache";
-

-
// Creates a function that memoizes its result using an LRU cache.
-
//
-
// The cache key is a string created from the arguments using
-
// `makeKey`.
-
export function cached<Args extends unknown[], V>(
-
  f: (...args: Args) => Promise<V>,
-
  makeKey: (...args: Args) => string,
-
  options?: LruCache.Options<string, { value: V }>,
-
): (...args: Args) => Promise<V> {
-
  const cache = new LruCache(options || { max: 500 });
-
  return async function (...args: Args): Promise<V> {
-
    const key = makeKey(...args);
-
    const cached = cache.get(key);
-

-
    if (cached === undefined) {
-
      const value = await f(...args);
-
      cache.set(key, { value });
-
      return value;
-
    } else {
-
      return cached.value;
-
    }
-
  };
-
}
modified src/lib/config.ts
@@ -1,7 +1,6 @@
import configJson from "@app/config.json";

export interface Config {
-
  walletConnect: { bridge: string };
  reactions: string[];
  seeds: {
    pinned: { host: string; emoji: string }[];
@@ -18,7 +17,6 @@ export interface Config {
function getConfig(): Config {
  if (window.VITEST) {
    return {
-
      walletConnect: { bridge: "" },
      reactions: [],
      seeds: {
        pinned: [],
deleted src/lib/ethereum/contractAbis.json
@@ -1,48 +0,0 @@
-
{
-
  "ens": ["function owner(bytes32 node) view returns (address)"],
-
  "faucet": [
-
    "function lastWithdrawalByUser(address) view returns (uint256)",
-
    "function maxWithdrawAmount() view returns (uint256)",
-
    "function calculateTimeLock(uint256) view returns (uint256)",
-
    "function withdraw(address, uint256)"
-
  ],
-
  "org": ["function owner() view returns (address)"],
-
  "registrar": [
-
    "function rad() view returns (address)",
-
    "function radNode() view returns (bytes32)",
-
    "function minCommitmentAge() view returns (uint256)",
-
    "function registrationFeeRad() view returns (uint256)",
-
    "function commit(bytes32)",
-
    "function commitWithPermit(bytes32, address, uint256, uint256, uint8, bytes32, bytes32)",
-
    "function register(string, address, uint256)",
-
    "function valid(string) pure returns (bool)",
-
    "function available(string) view returns (bool)"
-
  ],
-
  "resolver": [
-
    "function multicall(bytes[] calldata data) returns(bytes[] memory results)",
-
    "function setAddr(bytes32 node, address addr)",
-
    "function setText(bytes32 node, string calldata key, string calldata value)"
-
  ],
-
  "reverseRegistrar": ["function setName(string) returns (bytes32)"],
-
  "token": [
-
    "function balanceOf(address) view returns (uint256)",
-
    "function approve(address, uint256) returns (bool)",
-
    "function allowance(address, address) view returns (uint256)",
-
    "function DOMAIN_SEPARATOR() view returns (bytes32)",
-
    "function name() pure returns (string)",
-
    "function symbol() pure returns (string)",
-
    "function nonces(address) view returns (uint256)"
-
  ],
-
  "vesting": [
-
    "function token() view returns (address)",
-
    "function totalVestingAmount() view returns (uint256)",
-
    "function vestingStartTime() view returns (uint256)",
-
    "function vestingPeriod() view returns (uint256)",
-
    "function cliffPeriod() view returns (uint256)",
-
    "function beneficiary() view returns (address)",
-
    "function interrupted() view returns (bool)",
-
    "function withdrawn() view returns (uint256)",
-
    "function withdrawableBalance() view returns (uint256)",
-
    "function withdrawVested()"
-
  ]
-
}
deleted src/lib/ethereum/networks/goerli.json
@@ -1,16 +0,0 @@
-
{
-
  "name": "goerli",
-
  "chainId": 5,
-
  "registrar": {
-
    "domain": "radicle-goerli.eth",
-
    "address": "0xD88303A92577bFDF5A82FddeF342F3A27A972405"
-
  },
-
  "radToken": {
-
    "address": "0x3EE94D192397aAFAe438C9803825eb1Aa4402e09",
-
    "faucet": "0xc627191d2BB8839eAcbb7191f9500B84d201A066"
-
  },
-
  "reverseRegistrar": {
-
    "address": "0x9a879320A9F7ad2BBb02063d67baF5551D6BD8B0"
-
  },
-
  "alchemy": { "key": "1T6h-0rxu7SRzKEtmukIoxaJOXazLDNs" }
-
}
deleted src/lib/ethereum/networks/homestead.json
@@ -1,15 +0,0 @@
-
{
-
  "name": "homestead",
-
  "chainId": 1,
-
  "registrar": {
-
    "domain": "radicle.eth",
-
    "address": "0x37723287Ae6F34866d82EE623401f92Ec9013154"
-
  },
-
  "radToken": {
-
    "address": "0x31c8EAcBFFdD875c74b94b077895Bd78CF1E64A3"
-
  },
-
  "reverseRegistrar": {
-
    "address": "0x084b1c3C81545d370f3634392De611CaaBFf8148"
-
  },
-
  "alchemy": { "key": "cQFlLK8EokIGlJhd_soImwEyUoC7Ec8r" }
-
}
deleted src/lib/faucet.ts
@@ -1,80 +0,0 @@
-
import type { TransactionResponse } from "@ethersproject/providers";
-
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
-
import type { Wallet } from "@app/lib/wallet";
-
import type { WalletConnectSigner } from "@app/lib/walletConnectSigner";
-

-
import * as ethers from "ethers";
-

-
import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json";
-
import { assert } from "@app/lib/error";
-
import { toWei } from "@app/lib/utils";
-

-
type Signer = (ethers.Signer & TypedDataSigner) | WalletConnectSigner | null;
-

-
export async function withdraw(
-
  amount: string,
-
  signer: Signer,
-
  wallet: Wallet,
-
): Promise<TransactionResponse> {
-
  assert(signer);
-
  assert(wallet.radToken.faucet);
-

-
  const faucet = new ethers.Contract(
-
    wallet.radToken.faucet,
-
    ethereumContractAbis.faucet,
-
    signer,
-
  );
-

-
  return faucet.withdraw(wallet.radToken.address, toWei(amount));
-
}
-

-
export async function getMaxWithdrawAmount(
-
  signer: Signer,
-
  wallet: Wallet,
-
): Promise<ethers.BigNumber> {
-
  assert(signer);
-
  assert(wallet.radToken.faucet);
-

-
  const faucet = new ethers.Contract(
-
    wallet.radToken.faucet,
-
    ethereumContractAbis.faucet,
-
    signer,
-
  );
-

-
  return faucet.maxWithdrawAmount();
-
}
-

-
export async function calculateTimeLock(
-
  amount: string,
-
  signer: Signer,
-
  wallet: Wallet,
-
): Promise<ethers.BigNumber> {
-
  assert(signer);
-
  assert(wallet.radToken.faucet);
-

-
  const faucet = new ethers.Contract(
-
    wallet.radToken.faucet,
-
    ethereumContractAbis.faucet,
-
    signer,
-
  );
-

-
  return faucet.calculateTimeLock(toWei(amount));
-
}
-

-
export async function lastWithdrawalByUser(
-
  signer: Signer,
-
  wallet: Wallet,
-
): Promise<ethers.BigNumber> {
-
  assert(signer);
-
  assert(wallet.radToken.faucet);
-

-
  const address = signer.getAddress();
-

-
  const faucet = new ethers.Contract(
-
    wallet.radToken.faucet,
-
    ethereumContractAbis.faucet,
-
    signer,
-
  );
-

-
  return faucet.lastWithdrawalByUser(address);
-
}
deleted src/lib/profile.ts
@@ -1,267 +0,0 @@
-
import type { EnsProfile } from "@app/lib/registrar";
-
import type { Seed, InvalidSeed } from "@app/lib/seed";
-
import type { TransactionResponse } from "@ethersproject/providers";
-
import type { Wallet } from "@app/lib/wallet";
-

-
import * as ethers from "ethers";
-

-
import * as cache from "@app/lib/cache";
-
import * as utils from "@app/lib/utils";
-
import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json";
-
import { NotFoundError, MissingReverseRecord } from "@app/lib/error";
-
import { assert } from "@app/lib/error";
-
import { cached } from "@app/lib/cache";
-
import {
-
  isAddress,
-
  resolveEnsProfile,
-
  parseUsername,
-
  AddressType,
-
  identifyAddress,
-
  isFulfilled,
-
} from "@app/lib/utils";
-

-
class Org {
-
  address: string;
-
  owner: string;
-
  name?: string | null;
-

-
  constructor(address: string, owner: string, name?: string | null) {
-
    assert(ethers.utils.isAddress(address), "address must be valid");
-

-
    this.address = address.toLowerCase(); // Don't store address checksum.
-
    this.owner = owner;
-
    this.name = name;
-
  }
-

-
  static async get(addressOrName: string, wallet: Wallet): Promise<Org | null> {
-
    const org = await getOrgContract(addressOrName, wallet);
-

-
    try {
-
      const [owner, resolved] = await resolveOrgOwner(org);
-

-
      // If what is resolved is not the same as the input, it's because we
-
      // were given a name.
-
      if (utils.isAddressEqual(addressOrName, resolved)) {
-
        return new Org(resolved, owner, null);
-
      } else {
-
        return new Org(resolved, owner, addressOrName);
-
      }
-
    } catch (e) {
-
      console.error(e);
-
      return null;
-
    }
-
  }
-
}
-

-
export class User {
-
  address: string;
-

-
  constructor(address: string) {
-
    assert(ethers.utils.isAddress(address), "address must be valid");
-

-
    this.address = address.toLowerCase(); // Don't store address checksum.
-
  }
-

-
  async setName(name: string, wallet: Wallet): Promise<TransactionResponse> {
-
    assert(wallet.signer);
-

-
    const reverseRegistrar = new ethers.Contract(
-
      wallet.reverseRegistrar.address,
-
      ethereumContractAbis.reverseRegistrar,
-
      wallet.signer,
-
    );
-
    return reverseRegistrar.setName(name);
-
  }
-
}
-

-
const getOrgContract = cache.cached(
-
  async (addressOrName: string, wallet: Wallet) => {
-
    return new ethers.Contract(
-
      addressOrName,
-
      ethereumContractAbis.org,
-
      wallet.provider,
-
    );
-
  },
-
  addressOrName => addressOrName,
-
);
-

-
const resolveOrgOwner = cache.cached(
-
  async (org: ethers.Contract) => {
-
    return await Promise.all([org.owner(), org.resolvedAddress]);
-
  },
-
  org => org.address,
-
);
-

-
export interface IProfile {
-
  address: string;
-
  type: AddressType;
-
  ens?: EnsProfile;
-
  org?: Org;
-
}
-

-
export enum ProfileType {
-
  Full,
-
  Minimal,
-
  Project,
-
}
-

-
export class Profile {
-
  profile: IProfile;
-

-
  constructor(profile: IProfile) {
-
    this.profile = profile;
-
  }
-

-
  // Get the Ethereum address
-
  get address(): string {
-
    return this.profile.ens?.address ?? this.profile.address;
-
  }
-

-
  // Get radicle link id.
-
  get id(): string | undefined {
-
    return this.profile.ens?.id;
-
  }
-

-
  // Get the address type
-
  get type(): AddressType {
-
    return this.profile.type;
-
  }
-

-
  // Get the org instance
-
  get org(): Org | undefined {
-
    return this.profile.org;
-
  }
-

-
  // Get the ENS profile
-
  get ens(): EnsProfile | undefined {
-
    return this.profile.ens;
-
  }
-

-
  get github(): string | undefined {
-
    if (this.profile?.ens?.github) {
-
      return parseUsername(this.profile.ens.github);
-
    } else {
-
      return undefined;
-
    }
-
  }
-

-
  get twitter(): string | undefined {
-
    if (this.profile?.ens?.twitter) {
-
      return parseUsername(this.profile.ens.twitter);
-
    } else {
-
      return undefined;
-
    }
-
  }
-

-
  get url(): string | undefined {
-
    if (this.profile?.ens?.url) return this.profile.ens.url;
-
    else return undefined;
-
  }
-

-
  get name(): string | undefined {
-
    if (this.profile?.ens?.name) return this.profile.ens.name;
-
    else return undefined;
-
  }
-

-
  get avatar(): string | undefined {
-
    if (this.profile?.ens?.avatar) {
-
      return this.profile.ens.avatar;
-
    } else {
-
      return undefined;
-
    }
-
  }
-

-
  // We add null here to differentiate between a `undefined` and a invalid / null seed
-
  get seed(): Seed | InvalidSeed | null {
-
    return this.profile?.ens?.seed ?? null;
-
  }
-

-
  // Get the name, and if not available, the address.
-
  get addressOrName(): string {
-
    return this.name ?? this.address;
-
  }
-

-
  // Keeping this function private since the desired entrypoint is .get()
-
  // All addresses returned from this function should be lowercase.
-
  private static async lookupProfile(
-
    addressOrName: string,
-
    profileType: ProfileType,
-
    wallet: Wallet,
-
  ): Promise<IProfile> {
-
    let type = AddressType.EOA;
-
    let org: Org | null = null;
-
    const ens = await resolveEnsProfile(addressOrName, profileType, wallet);
-

-
    if (ens) {
-
      if (ens.address) {
-
        type = await identifyAddress(ens.address, wallet);
-

-
        if (type === AddressType.Org) {
-
          org = await Org.get(ens.address, wallet);
-
        }
-

-
        return {
-
          address: ens.address.toLowerCase(),
-
          type,
-
          ens: { ...ens, address: ens.address.toLowerCase() },
-
          org: org ?? undefined,
-
        };
-
      }
-
      throw new MissingReverseRecord(`No address set for ${addressOrName}`);
-
    } else if (isAddress(addressOrName)) {
-
      const address = addressOrName.toLowerCase();
-

-
      type = await identifyAddress(address, wallet);
-
      if (type === AddressType.Org) {
-
        org = await Org.get(address, wallet);
-
      }
-

-
      try {
-
        return {
-
          address,
-
          type,
-
          org: org ?? undefined,
-
        };
-
      } catch (e: any) {
-
        console.error(e);
-

-
        return { address, type, org: org ?? undefined };
-
      }
-
    }
-
    throw new NotFoundError(`Not able to resolve profile for ${addressOrName}`);
-
  }
-

-
  static async getMulti(
-
    addressesOrNames: string[],
-
    wallet: Wallet,
-
  ): Promise<Profile[]> {
-
    const profilePromises = addressesOrNames.map(addressOrName =>
-
      this.lookupProfile(addressOrName, ProfileType.Minimal, wallet),
-
    );
-
    const profiles = await Promise.allSettled(profilePromises);
-
    return profiles
-
      .filter(isFulfilled)
-
      .map(profile => new Profile(profile.value));
-
  }
-

-
  static async get(
-
    addressOrName: string,
-
    profileType: ProfileType,
-
    wallet: Wallet,
-
  ): Promise<Profile> {
-
    const profile = await this.lookupProfile(
-
      addressOrName,
-
      profileType,
-
      wallet,
-
    );
-
    return new Profile(profile);
-
  }
-
}
-

-
export const getBalance = cached(
-
  async (address: string, wallet: Wallet) => {
-
    return await wallet.provider.getBalance(address);
-
  },
-
  address => address,
-
  { max: 1000 },
-
);
modified src/lib/project.ts
@@ -1,8 +1,8 @@
import type { Commit, CommitHeader, CommitsHistory } from "@app/lib/commit";
-
import type { Wallet } from "@app/lib/wallet";
-
import { type Host, Request } from "@app/lib/api";
+
import type { Host } from "@app/lib/api";
+
import type { ProjectResult } from "@app/lib/search";

-
import { Profile, ProfileType } from "@app/lib/profile";
+
import { Request } from "@app/lib/api";
import { Seed, defaultSeedPort } from "@app/lib/seed";
import { isFulfilled, isOid, isRadicleId } from "@app/lib/utils";

@@ -158,7 +158,6 @@ export class Project implements ProjectInfo {
  seed: Seed;
  peers: Peer[];
  branches: Branches;
-
  profile: Profile | null;
  // At the moment we still have seed nodes which won't return neither patches or issues
  patches?: number;
  issues?: number;
@@ -169,7 +168,6 @@ export class Project implements ProjectInfo {
    seed: Seed,
    peers: Peer[],
    branches: Branches,
-
    profile: Profile | null,
  ) {
    this.id = id;
    this.head = info.head;
@@ -183,7 +181,6 @@ export class Project implements ProjectInfo {
    this.branches = branches;
    this.patches = info.patches;
    this.issues = info.issues;
-
    this.profile = profile;
  }

  async getRoot(
@@ -403,26 +400,16 @@ export class Project implements ProjectInfo {

  static async get(
    id: string,
-
    peer: string | null,
-
    profileName: string | null,
-
    seedHost: string | null,
-
    wallet: Wallet,
+
    seedHost: string,
+
    peer?: string,
  ): Promise<Project> {
-
    const profile = profileName
-
      ? await Profile.get(profileName, ProfileType.Project, wallet)
-
      : null;
-

-
    const [host, port] = seedHost?.includes(":")
+
    const [host, port] = seedHost.includes(":")
      ? seedHost.split(":")
      : [seedHost, defaultSeedPort];

-
    const seed = profile
-
      ? profile.seed
-
      : host
-
      ? await Seed.lookup(host, Number(port))
-
      : null;
+
    const seed = await Seed.lookup(host, Number(port));

-
    if (!profile && !seed) {
+
    if (!seed) {
      throw new Error("Couldn't load project");
    }
    if (!seed?.valid) {
@@ -455,12 +442,12 @@ export class Project implements ProjectInfo {
      }
    }

-
    return new Project(id, info, seed, peers, remote.heads, profile);
+
    return new Project(id, info, seed, peers, remote.heads);
  }

  static async getMulti(
    projs: { nameOrId: Id; seed: string }[],
-
  ): Promise<{ info: ProjectInfo; seed: Host }[]> {
+
  ): Promise<ProjectResult[]> {
    const promises = [];

    for (const proj of projs) {
deleted src/lib/registrar.ts
@@ -1,423 +0,0 @@
-
import type { BigNumber } from "ethers";
-
import type { EnsResolver } from "@ethersproject/providers";
-
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
-
import type { Wallet } from "@app/lib/wallet";
-

-
import { ethers } from "ethers";
-
import { writable } from "svelte/store";
-

-
import * as cache from "@app/lib/cache";
-
import * as session from "@app/lib/session";
-
import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json";
-
import { Failure } from "@app/lib/error";
-
import { Seed, InvalidSeed } from "@app/lib/seed";
-
import { assert } from "@app/lib/error";
-
import { isFulfilled, unixTime } from "@app/lib/utils";
-

-
export interface Registration {
-
  profile: EnsProfile;
-
  resolver: EnsResolver;
-
}
-

-
export interface EnsProfile {
-
  name: string;
-
  id?: string;
-
  owner?: string;
-
  address?: string;
-
  seed?: Seed | InvalidSeed;
-
  url?: string;
-
  avatar?: string;
-
  twitter?: string;
-
  github?: string;
-
}
-

-
export enum State {
-
  Failed = -1,
-
  Connecting,
-
  SigningPermit,
-
  SigningCommit,
-
  Committing,
-
  WaitingToRegister,
-
  SigningRegister,
-
  Registering,
-
  Registered,
-
}
-

-
export type Connection =
-
  | { connection: State.Failed }
-
  | { connection: State.Connecting }
-
  | { connection: State.SigningPermit }
-
  | { connection: State.SigningCommit }
-
  | { connection: State.Committing }
-
  | {
-
      connection: State.WaitingToRegister;
-
      commitmentBlock: number;
-
      minAge: number;
-
    }
-
  | { connection: State.SigningRegister }
-
  | { connection: State.Registering }
-
  | { connection: State.Registered };
-

-
export const state = writable<Connection>({ connection: State.Connecting });
-

-
window.registrarState = state;
-

-
export async function getRegistration(
-
  name: string,
-
  wallet: Wallet,
-
  resolver?: EnsResolver | null,
-
): Promise<Registration | null> {
-
  name = name.toLowerCase();
-

-
  if (!resolver) {
-
    resolver = await getResolver(name, wallet);
-

-
    if (!resolver) {
-
      return null;
-
    }
-
  }
-

-
  const meta = await Promise.allSettled([
-
    getAddress(resolver),
-
    getText(resolver, "avatar"),
-
    getText(resolver, "url"),
-
    getText(resolver, "eth.radicle.id"),
-
    getText(resolver, "eth.radicle.seed.id"),
-
    getText(resolver, "eth.radicle.seed.host"),
-
    getText(resolver, "eth.radicle.seed.git"),
-
    getText(resolver, "eth.radicle.seed.api"),
-
    getText(resolver, "com.twitter"),
-
    getText(resolver, "com.github"),
-
  ]);
-

-
  const [
-
    address,
-
    avatar,
-
    url,
-
    id,
-
    seedId,
-
    seedHost,
-
    seedGit,
-
    seedApi,
-
    twitter,
-
    github,
-
  ] = meta.filter(isFulfilled).map(r => (r.value ? r.value : undefined));
-

-
  const profile: EnsProfile = {
-
    name,
-
    id,
-
    url,
-
    avatar,
-
    address,
-
    twitter,
-
    github,
-
  };
-

-
  // If no seed provided profile.seed ends up being undefined
-
  if (seedHost && seedId) {
-
    try {
-
      profile.seed = new Seed({
-
        host: seedHost,
-
        id: seedId,
-
        git: seedGit,
-
        addr: seedApi,
-
      });
-
    } catch (e: any) {
-
      console.debug(e, seedHost, seedId);
-
      profile.seed = new InvalidSeed(seedHost, seedId);
-
    }
-
  }
-

-
  return { resolver, profile };
-
}
-

-
export async function getAvatar(
-
  name: string,
-
  wallet: Wallet,
-
  resolver?: EnsResolver | null,
-
): Promise<string | null> {
-
  name = name.toLowerCase();
-

-
  resolver = resolver ?? (await getResolver(name, wallet));
-
  if (!resolver) {
-
    return null;
-
  }
-
  return getText(resolver, "avatar");
-
}
-

-
export async function getSeed(
-
  name: string,
-
  wallet: Wallet,
-
  resolver?: EnsResolver | null,
-
): Promise<Seed | InvalidSeed | null> {
-
  name = name.toLowerCase();
-

-
  resolver = resolver ?? (await getResolver(name, wallet));
-
  if (!resolver) {
-
    return null;
-
  }
-

-
  const [id, host, api] = await Promise.all([
-
    getText(resolver, "eth.radicle.seed.id"),
-
    getText(resolver, "eth.radicle.seed.host"),
-
    getText(resolver, "eth.radicle.seed.api"),
-
  ]);
-

-
  if (!host || !id) {
-
    console.debug("getSeed: No seed host or id provided");
-
    return null;
-
  }
-

-
  try {
-
    return new Seed({ host, id, addr: api });
-
  } catch (e: any) {
-
    console.debug(e, host, id);
-
    return new InvalidSeed(id, host);
-
  }
-
}
-

-
export function registrar(wallet: Wallet): ethers.Contract {
-
  return new ethers.Contract(
-
    wallet.registrar.address,
-
    ethereumContractAbis.registrar,
-
    wallet.provider,
-
  );
-
}
-

-
export async function registrationFee(wallet: Wallet): Promise<BigNumber> {
-
  return await registrar(wallet).registrationFeeRad();
-
}
-

-
export async function registerName(
-
  name: string,
-
  owner: string,
-
  wallet: Wallet,
-
): Promise<void> {
-
  assert(wallet.signer, "signer is not available");
-

-
  if (!name) return;
-

-
  name = name.toLowerCase();
-

-
  const commitmentJson = window.localStorage.getItem("commitment");
-
  const commitment = commitmentJson && JSON.parse(commitmentJson);
-

-
  try {
-
    // Try to recover an existing commitment.
-
    if (commitment && commitment.name === name && commitment.owner === owner) {
-
      await register(name, owner, commitment.salt, wallet);
-
    } else {
-
      await commitAndRegister(name, owner, wallet);
-
    }
-
  } catch (e: any) {
-
    throw {
-
      type: e.type || Failure.TransactionFailed,
-
      message: e.message,
-
      txHash: e.txHash,
-
    };
-
  }
-
}
-

-
async function commitAndRegister(
-
  name: string,
-
  owner: string,
-
  wallet: Wallet,
-
): Promise<void> {
-
  const salt = ethers.utils.randomBytes(32);
-
  const minAge = (await registrar(wallet).minCommitmentAge()).toNumber();
-
  const fee = await registrationFee(wallet);
-
  // Avoids gas spent by the owner, trying to commit to a name and not having
-
  // enough RAD balance
-
  if ((await wallet.token.balanceOf(owner)).lt(fee)) {
-
    throw {
-
      type: Failure.InsufficientBalance,
-
      message: "Not enough RAD funds",
-
    };
-
  }
-
  name = name.toLowerCase();
-

-
  await commit(name, owner, salt, fee, minAge, wallet);
-
  await register(name, owner, salt, wallet);
-
}
-

-
async function commit(
-
  name: string,
-
  owner: string,
-
  salt: Uint8Array,
-
  fee: BigNumber,
-
  minAge: number,
-
  wallet: Wallet,
-
): Promise<void> {
-
  assert(wallet.signer, "signer is not available");
-

-
  const commitment = makeCommitment(name, owner, salt);
-
  const spender = wallet.registrar.address;
-
  const deadline = ethers.BigNumber.from(unixTime()).add(3600); // Expire one hour from now.
-
  const token = wallet.token;
-

-
  let tx = null;
-

-
  if (fee.isZero()) {
-
    state.set({ connection: State.SigningCommit });
-

-
    tx = await registrar(wallet)
-
      .connect(wallet.signer)
-
      .commit(commitment, { gasLimit: 180000 });
-
  } else {
-
    const signature = await permitSignature(
-
      wallet.signer,
-
      token,
-
      spender,
-
      fee,
-
      deadline,
-
    );
-

-
    state.set({ connection: State.SigningCommit });
-

-
    tx = await registrar(wallet)
-
      .connect(wallet.signer)
-
      .commitWithPermit(
-
        commitment,
-
        owner,
-
        fee,
-
        deadline,
-
        signature.v,
-
        signature.r,
-
        signature.s,
-
        { gasLimit: 180000 },
-
      );
-
  }
-

-
  state.set({ connection: State.Committing });
-

-
  const receipt = await tx.wait(1);
-
  session.state.updateBalance(fee.mul(-1));
-

-
  // Save commitment in local storage in case the user refreshes or closes
-
  // the page.
-
  window.localStorage.setItem(
-
    "commitment",
-
    JSON.stringify({
-
      name: name,
-
      owner: owner,
-
      salt: ethers.utils.hexlify(salt),
-
    }),
-
  );
-

-
  state.set({
-
    connection: State.WaitingToRegister,
-
    commitmentBlock: receipt.blockNumber,
-
    minAge,
-
  });
-
  await tx.wait(minAge + 1);
-
}
-

-
async function permitSignature(
-
  owner: ethers.Signer & TypedDataSigner,
-
  token: ethers.Contract,
-
  spenderAddr: string,
-
  value: ethers.BigNumberish,
-
  deadline: ethers.BigNumberish,
-
): Promise<ethers.Signature> {
-
  assert(owner.provider, "provider is not available");
-
  state.set({ connection: State.SigningPermit });
-

-
  const ownerAddr = await owner.getAddress();
-
  const nonce = await token.nonces(ownerAddr);
-
  const chainId = (await owner.provider.getNetwork()).chainId;
-

-
  const domain = {
-
    name: await token.name(),
-
    chainId,
-
    verifyingContract: token.address,
-
  };
-
  const types = {
-
    Permit: [
-
      { name: "owner", type: "address" },
-
      { name: "spender", type: "address" },
-
      { name: "value", type: "uint256" },
-
      { name: "nonce", type: "uint256" },
-
      { name: "deadline", type: "uint256" },
-
    ],
-
  };
-
  const values = {
-
    owner: ownerAddr,
-
    spender: spenderAddr,
-
    value: value,
-
    nonce: nonce,
-
    deadline: deadline,
-
  };
-
  const sig = await owner._signTypedData(domain, types, values);
-

-
  return ethers.utils.splitSignature(sig);
-
}
-

-
async function register(
-
  name: string,
-
  owner: string,
-
  salt: Uint8Array,
-
  wallet: Wallet,
-
) {
-
  assert(wallet.signer, "signer is not available");
-
  state.set({ connection: State.SigningRegister });
-

-
  const tx = await registrar(wallet)
-
    .connect(wallet.signer)
-
    .register(name, owner, ethers.BigNumber.from(salt), { gasLimit: 150000 });
-
  state.set({ connection: State.Registering });
-

-
  console.debug("Sent", tx);
-

-
  await tx.wait();
-
  window.localStorage.removeItem("commitment");
-
  state.set({ connection: State.Registered });
-
}
-

-
function makeCommitment(name: string, owner: string, salt: Uint8Array): string {
-
  const bytes = ethers.utils.concat([
-
    ethers.utils.toUtf8Bytes(name),
-
    ethers.utils.getAddress(owner),
-
    ethers.BigNumber.from(salt).toHexString(),
-
  ]);
-
  return ethers.utils.keccak256(bytes);
-
}
-

-
export async function getOwner(name: string, wallet: Wallet): Promise<string> {
-
  const ensAddr = wallet.provider.network.ensAddress;
-
  if (!ensAddr) {
-
    throw new Error("ENS address is not defined");
-
  }
-

-
  const registry = new ethers.Contract(
-
    ensAddr,
-
    ethereumContractAbis.ens,
-
    wallet.provider,
-
  );
-
  const owner = await registry.owner(ethers.utils.namehash(name));
-

-
  return owner;
-
}
-

-
export const getResolver = cache.cached(
-
  async (name: string, wallet: Wallet) => {
-
    return await wallet.provider.getResolver(name);
-
  },
-
  name => name,
-
  { max: 1000 },
-
);
-

-
export const getText = cache.cached(
-
  async (resolver: EnsResolver, key: string) => {
-
    return await resolver.getText(key);
-
  },
-
  (resolver, key) => `${resolver.name} ${key}`,
-
  { max: 1000 },
-
);
-

-
export const getAddress = cache.cached(
-
  async (resolver: EnsResolver) => {
-
    return await resolver.getAddress();
-
  },
-
  resolver => resolver.name,
-
  { max: 1000 },
-
);
deleted src/lib/resolver.ts
@@ -1,64 +0,0 @@
-
import type { EnsResolver } from "@ethersproject/providers";
-
import type { TransactionResponse } from "@ethersproject/providers";
-
import type { Wallet } from "@app/lib/wallet";
-

-
import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json";
-
import { assert } from "@app/lib/error";
-
import { ethers } from "ethers";
-

-
export type EnsRecord = { name: string; value: string };
-

-
export async function setRecords(
-
  name: string,
-
  records: EnsRecord[],
-
  resolver: EnsResolver,
-
  wallet: Wallet,
-
): Promise<TransactionResponse> {
-
  assert(wallet.signer, "no signer available");
-

-
  const resolverContract = new ethers.Contract(
-
    resolver.address,
-
    ethereumContractAbis.resolver,
-
    wallet.signer,
-
  );
-
  const node = ethers.utils.namehash(name);
-

-
  const calls = [];
-
  const iface = new ethers.utils.Interface(ethereumContractAbis.resolver);
-

-
  for (const r of records) {
-
    switch (r.name) {
-
      case "address":
-
        calls.push(iface.encodeFunctionData("setAddr", [node, r.value]));
-
        break;
-
      case "url":
-
      case "avatar":
-
        calls.push(
-
          iface.encodeFunctionData("setText", [node, r.name, r.value]),
-
        );
-
        break;
-
      case "github":
-
      case "twitter":
-
        calls.push(
-
          iface.encodeFunctionData("setText", [node, "com." + r.name, r.value]),
-
        );
-
        break;
-
      case "id":
-
      case "seed.id":
-
      case "seed.host":
-
      case "seed.git":
-
      case "seed.api":
-
        calls.push(
-
          iface.encodeFunctionData("setText", [
-
            node,
-
            "eth.radicle." + r.name,
-
            r.value,
-
          ]),
-
        );
-
        break;
-
      default:
-
        console.error(`unknown field "${r.name}"`);
-
    }
-
  }
-
  return resolverContract.multicall(calls);
-
}
modified src/lib/router.ts
@@ -148,42 +148,6 @@ function pathToRoute(path: string): Route | null {

  const resource = segments.shift();
  switch (resource) {
-
    case "registrations": {
-
      const nameOrDomain = segments.shift();
-
      const view = segments.shift();
-
      const retry = url.searchParams.get("retry");
-

-
      if (nameOrDomain) {
-
        if (!view) {
-
          return {
-
            resource: "registrations",
-
            params: {
-
              view: {
-
                resource: "view",
-
                params: { nameOrDomain, retry: retry === "true" },
-
              },
-
            },
-
          };
-
        }
-
      }
-
      return {
-
        resource: "registrations",
-
        params: { view: { resource: "form" } },
-
      };
-
    }
-
    case "faucet": {
-
      return { resource: "faucet" };
-
    }
-
    case "vesting": {
-
      const contract = segments.shift();
-
      if (contract) {
-
        return {
-
          resource: "vesting",
-
          params: { view: { resource: "view", params: { contract } } },
-
        };
-
      }
-
      return { resource: "vesting", params: { view: { resource: "form" } } };
-
    }
    case "seeds": {
      const host = segments.shift();
      if (host) {
@@ -196,12 +160,11 @@ function pathToRoute(path: string): Route | null {
                view: { resource: "tree" },
                id,
                peer: undefined,
-
                profile: undefined,
                seed: host,
              },
            };
          }
-
          const params = resolveProjectRoute(url, id, segments);
+
          const params = resolveProjectRoute(url, host, id, segments);
          if (params) {
            return {
              resource: "projects",
@@ -218,41 +181,11 @@ function pathToRoute(path: string): Route | null {
      }
      return null;
    }
-
    case "":
+
    case "": {
      return { resource: "home" };
+
    }
    default: {
-
      if (resource) {
-
        const id = segments.shift();
-
        if (id) {
-
          if (segments.length === 0) {
-
            return {
-
              resource: "projects",
-
              params: {
-
                view: { resource: "tree" },
-
                id,
-
                peer: undefined,
-
                profile: resource,
-
                seed: undefined,
-
              },
-
            };
-
          } else {
-
            const params = resolveProjectRoute(url, id, segments);
-
            if (params) {
-
              return {
-
                resource: "projects",
-
                params: {
-
                  ...params,
-
                  id,
-
                  profile: resource,
-
                },
-
              };
-
            }
-
          }
-
          return null;
-
        }
-
        return { resource: "profile", params: { addressOrName: resource } };
-
      }
-
      return { resource: "home" };
+
      return null;
    }
  }
}
@@ -260,24 +193,10 @@ function pathToRoute(path: string): Route | null {
export function routeToPath(route: Route) {
  if (route.resource === "home") {
    return "/";
-
  } else if (route.resource === "faucet") {
-
    return "/faucet";
-
  } else if (route.resource === "vesting") {
-
    if (route.params.view.resource === "form") {
-
      return "/vesting";
-
    } else if (route.params.view.resource === "view") {
-
      return `/vesting/${route.params.view.params.contract}`;
-
    }
  } else if (route.resource === "seeds") {
    return `/seeds/${route.params.host}`;
  } else if (route.resource === "projects") {
-
    let hostPrefix;
-
    if (route.params.profile) {
-
      hostPrefix = `/${route.params.profile}`;
-
    } else {
-
      hostPrefix = `/seeds/${route.params.seed}`;
-
    }
-

+
    const hostPrefix = `/seeds/${route.params.seed}`;
    const content = `/${route.params.view.resource}`;

    let peer = "";
@@ -328,14 +247,6 @@ export function routeToPath(route: Route) {
    } else {
      return `${hostPrefix}/${route.params.id}${peer}${content}`;
    }
-
  } else if (route.resource === "registrations") {
-
    if (route.params.view.resource === "form") {
-
      return `/registrations`;
-
    } else if (route.params.view.resource === "view") {
-
      return `/registrations/${route.params.view.params.nameOrDomain}?retry=${route.params.view.params.retry}`;
-
    }
-
  } else if (route.resource === "profile") {
-
    return `/${route.params.addressOrName}`;
  } else if (route.resource === "404") {
    return route.params.url;
  } else {
@@ -345,6 +256,7 @@ export function routeToPath(route: Route) {

function resolveProjectRoute(
  url: URL,
+
  seed: string,
  id: string,
  segments: string[],
): ProjectsParams | null {
@@ -361,6 +273,7 @@ function resolveProjectRoute(
    return {
      view: { resource: "tree" },
      id,
+
      seed,
      peer,
      path: undefined,
      revision: undefined,
@@ -373,6 +286,7 @@ function resolveProjectRoute(
    return {
      view: { resource: "history" },
      id,
+
      seed,
      peer,
      path: undefined,
      revision: undefined,
@@ -383,6 +297,7 @@ function resolveProjectRoute(
    return {
      view: { resource: "commits" },
      id,
+
      seed,
      peer,
      path: undefined,
      revision: undefined,
@@ -395,6 +310,7 @@ function resolveProjectRoute(
      return {
        view: { resource: "patch", params: { patch } },
        id,
+
        seed,
        peer,
        path: undefined,
        search: undefined,
@@ -404,6 +320,7 @@ function resolveProjectRoute(
      return {
        view: { resource: "patches" },
        id,
+
        seed,
        peer,
        search: sanitizeQueryString(url.search),
        path: undefined,
@@ -416,6 +333,7 @@ function resolveProjectRoute(
      return {
        view: { resource: "issue", params: { issue } },
        id,
+
        seed,
        peer,
        path: undefined,
        revision: undefined,
@@ -425,6 +343,7 @@ function resolveProjectRoute(
      return {
        view: { resource: "issues" },
        id,
+
        seed,
        peer,
        search: sanitizeQueryString(url.search),
        path: undefined,
modified src/lib/router/definitions.ts
@@ -1,13 +1,7 @@
-
import type { VestingInfo } from "@app/lib/vesting";
-

export type Route =
  | ProjectRoute
-
  | RegistrationRoute
-
  | VestingRoute
-
  | { resource: "faucet" }
  | { resource: "home" }
  | { resource: "404"; params: { url: string } }
-
  | { resource: "profile"; params: { addressOrName: string } }
  | { resource: "seeds"; params: { host: string } };

export interface ProjectsParams {
@@ -20,37 +14,14 @@ export interface ProjectsParams {
    | { resource: "issues" }
    | { resource: "patch"; params: { patch: string } }
    | { resource: "patches" };
+
  seed: string;
  hash?: string;
  line?: string;
  path?: string;
  peer?: string;
-
  profile?: string;
  revision?: string;
  route?: string;
  search?: string;
-
  seed?: string;
-
}
-

-
export interface VestingParams {
-
  view:
-
    | { resource: "form" }
-
    | { resource: "view"; params: { contract: string; info?: VestingInfo } };
-
}
-

-
export interface RegistrationParams {
-
  view:
-
    | {
-
        resource: "form";
-
      }
-
    | {
-
        resource: "view";
-
        params: { nameOrDomain: string; retry: boolean };
-
      };
}

export type ProjectRoute = { resource: "projects"; params: ProjectsParams };
-
export type VestingRoute = { resource: "vesting"; params: VestingParams };
-
export type RegistrationRoute = {
-
  resource: "registrations";
-
  params: RegistrationParams;
-
};
modified src/lib/search.ts
@@ -1,36 +1,24 @@
import type { Host } from "@app/lib/api";
import type { ProjectInfo } from "@app/lib/project";
-
import type { Wallet } from "@app/lib/wallet";
-

-
import { ethers } from "ethers";

import * as utils from "@app/lib/utils";
-
import { Profile } from "@app/lib/profile";
import { Project } from "@app/lib/project";
import { config } from "@app/lib/config";

-
export interface ProjectsAndProfiles {
-
  projects: { info: ProjectInfo; seed: Host }[];
-
  profiles: Profile[];
+
export interface ProjectResult {
+
  info: ProjectInfo;
+
  seed: Host;
}

type SearchResult =
  | { type: "nothing" }
  | { type: "error"; message: string }
-
  | { type: "singleProfile"; id: string }
-
  | { type: "singleProject"; seedHost: string; id: string }
-
  | { type: "projectsAndProfiles"; projectsAndProfiles: ProjectsAndProfiles };
+
  | { type: "projects"; projects: ProjectResult[] };

export async function searchProjectsAndProfiles(
  query: string,
-
  wallet: Wallet,
): Promise<SearchResult> {
  try {
-
    // The query is a plain Ethereum address.
-
    if (ethers.utils.isAddress(query)) {
-
      return { type: "singleProfile", id: query };
-
    }
-

    const projectOnSeeds = config.seeds.pinned.map(seed => ({
      nameOrId: query,
      seed: seed.host,
@@ -40,74 +28,27 @@ export async function searchProjectsAndProfiles(
    if (utils.isRadicleId(query)) {
      const projects = await Project.getMulti(projectOnSeeds);

-
      if (projects.length === 1) {
-
        return {
-
          type: "singleProject",
-
          seedHost: projects[0].seed.host,
-
          id: query,
-
        };
-
      } else if (projects.length === 0) {
+
      if (projects.length === 0) {
        return { type: "nothing" };
      } else {
        return {
-
          type: "projectsAndProfiles",
-
          projectsAndProfiles: { projects, profiles: [] },
+
          type: "projects",
+
          projects,
        };
      }
    }

-
    // The query is either a project or a profile name.
-
    const normalizedQuery = query.toLowerCase();
-
    const projectsAndProfiles: ProjectsAndProfiles = {
-
      projects: [],
-
      profiles: [],
-
    };
-

+
    let projects: ProjectResult[] = [];
    try {
-
      const projects = await Project.getMulti(projectOnSeeds);
-
      projectsAndProfiles.projects.push(...projects);
+
      projects = await Project.getMulti(projectOnSeeds);
    } catch {
      // TODO: collect errors and forward to user.
    }

-
    try {
-
      let params: string[];
-
      if (utils.isENSName(normalizedQuery, wallet)) {
-
        params = [normalizedQuery];
-
      } else {
-
        params = [
-
          `${normalizedQuery}.${wallet.registrar.domain}`,
-
          `${normalizedQuery}.eth`,
-
        ];
-
      }
-
      const profiles = await Profile.getMulti(params, wallet);
-
      projectsAndProfiles.profiles.push(...profiles);
-
    } catch {
-
      // TODO: collect errors and forward to user.
-
    }
-

-
    const projectCount = projectsAndProfiles.projects.length;
-
    const profileCount = projectsAndProfiles.profiles.length;
-

-
    if (profileCount === 1 && projectCount === 0) {
-
      return {
-
        type: "singleProfile",
-
        id: projectsAndProfiles.profiles[0].address,
-
      };
-
    }
-

-
    if (profileCount === 0 && projectCount === 1) {
-
      return {
-
        type: "singleProject",
-
        seedHost: projectsAndProfiles.projects[0].seed.host,
-
        id: query,
-
      };
-
    }
-

-
    if (profileCount > 0 || projectCount > 0) {
+
    if (projects.length > 0) {
      return {
-
        type: "projectsAndProfiles",
-
        projectsAndProfiles,
+
        type: "projects",
+
        projects,
      };
    }

deleted src/lib/session.ts
@@ -1,399 +0,0 @@
-
import type { BigNumber } from "ethers";
-
import type { Readable } from "svelte/store";
-
import type {
-
  TransactionReceipt,
-
  TransactionResponse,
-
} from "@ethersproject/providers";
-
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
-
import type { WalletConnectSigner } from "@app/lib/walletConnectSigner";
-

-
import * as ethers from "ethers";
-
import { get, writable, derived } from "svelte/store";
-

-
import { Unreachable, assert, assertEq } from "@app/lib/error";
-
import { Wallet, getWallet } from "@app/lib/wallet";
-

-
export enum Connection {
-
  Disconnected,
-
  Connecting,
-
  Connected,
-
}
-

-
export type TxState =
-
  | { state: "signing" }
-
  | { state: "pending"; hash: string }
-
  | { state: "success"; hash: string; blockHash: string; blockNumber: number }
-
  | {
-
      state: "fail";
-
      hash: string;
-
      blockHash: string;
-
      blockNumber: number;
-
      error: string;
-
    }
-
  | null;
-

-
export type Signer = (ethers.Signer & TypedDataSigner) | WalletConnectSigner;
-

-
// Defines the type of signer we are using in the current session.
-
// Allows us to guard certain functionality for a specific signer.
-
enum SignerType {
-
  WalletConnect,
-
  MetaMask,
-
}
-

-
export type State =
-
  | { connection: Connection.Disconnected }
-
  | { connection: Connection.Connecting }
-
  | { connection: Connection.Connected; session: Session };
-

-
export interface Session {
-
  address: string;
-
  signer: Signer | null;
-
  signerType: SignerType;
-
  tokenBalance: BigNumber | null; // `null` means it isn't loaded yet.
-
  tx: TxState;
-
}
-

-
export interface Store extends Readable<State> {
-
  connectMetamask(wallet: Wallet): Promise<void>;
-
  connectWalletConnect(wallet: Wallet): Promise<void>;
-
  updateBalance(n: BigNumber): void;
-
  refreshBalance(wallet: Wallet): Promise<void>;
-
  setTxSigning(): void;
-
  setTxPending(tx: TransactionResponse): void;
-
  setTxConfirmed(tx: TransactionReceipt): void;
-
  setChangedAccount(address: string, signer: Signer): void;
-
}
-

-
export const loadState = (initial: State): Store => {
-
  const store = writable<State>(initial);
-

-
  return {
-
    subscribe: store.subscribe,
-
    connectMetamask: async (wallet: Wallet) => {
-
      assert(wallet.metamask.signer);
-
      // We use wallet.metamask.signer here, because wallet.signer is still null on page reload.
-
      const signer = wallet.metamask.signer;
-

-
      // Re-connect using previous session.
-
      if (wallet.metamask.connected) {
-
        const metamask = wallet.metamask.session;
-
        const tokenBalance: BigNumber = await wallet.token.balanceOf(
-
          metamask.address,
-
        );
-
        const session = {
-
          address: metamask.address,
-
          signer,
-
          signerType: SignerType.MetaMask,
-
          tokenBalance,
-
          tx: null,
-
        };
-

-
        store.set({ connection: Connection.Connected, session });
-
        wallet.setSigner(signer);
-

-
        return;
-
      }
-

-
      const state = get(store);
-

-
      assertEq(
-
        state.connection,
-
        Connection.Disconnected || Connection.Connecting,
-
      );
-
      store.set({ connection: Connection.Connecting });
-

-
      await window.ethereum.request({ method: "eth_requestAccounts" });
-
      const address = await signer.getAddress();
-

-
      wallet.setSigner(signer);
-

-
      try {
-
        // Closes the wallet modal.
-
        // TODO: We should move this into the session store.
-
        wallet.walletConnect.state.set({ state: "close" });
-

-
        const tokenBalance: BigNumber = await wallet.token.balanceOf(address);
-
        const session = {
-
          address,
-
          signer,
-
          signerType: SignerType.MetaMask,
-
          tokenBalance,
-
          tx: null,
-
        };
-

-
        store.set({
-
          connection: Connection.Connected,
-
          session,
-
        });
-
        saveMetamaskSession(session);
-
      } catch (e) {
-
        console.error(e);
-
      }
-
    },
-

-
    connectWalletConnect: async (wallet: Wallet) => {
-
      store.set({ connection: Connection.Connecting });
-
      // We fetch the walletConnect signer here, because wallet.signer is still null on page reload.
-
      const signer = wallet.getWalletConnectSigner();
-

-
      try {
-
        await wallet.walletConnect.client.connect();
-
        console.debug("WalletConnect: connected.");
-

-
        const address = await signer.getAddress();
-
        const tokenBalance: BigNumber = await wallet.token.balanceOf(address);
-
        const session = {
-
          address,
-
          signer,
-
          signerType: SignerType.WalletConnect,
-
          tokenBalance,
-
          tx: null,
-
        };
-
        const network = ethers.providers.getNetwork(
-
          signer.walletConnect.chainId,
-
        );
-

-
        // Instead of killing the WalletConnect session, we force the UI to change network
-
        if (network.chainId !== wallet.network.chainId) {
-
          wallet.changeNetwork(network.chainId);
-
        }
-

-
        wallet.walletConnect.client.on(
-
          "session_update",
-
          async (
-
            error,
-
            {
-
              params: [{ accounts, chainId }],
-
            }: { params: [{ accounts: [string]; chainId: number }] },
-
          ) => {
-
            if (error) {
-
              throw error;
-
            }
-

-
            try {
-
              // We update wallet to reflect the new signer address.
-
              const signer = wallet.getWalletConnectSigner();
-
              changeAccounts(accounts[0], signer);
-

-
              // Check the current chainId, and request Metamask to change, or reload the window to get the correct chain.
-
              if (chainId !== wallet.network.chainId) {
-
                if (session.signerType === SignerType.MetaMask) {
-
                  await window.ethereum.request({
-
                    method: "wallet_switchEthereumChain",
-
                    params: [{ chainId: ethers.utils.hexValue(chainId) }],
-
                  });
-
                } else {
-
                  window.location.reload();
-
                }
-
              }
-
            } catch (e) {
-
              console.error(e);
-
            }
-
          },
-
        );
-

-
        store.set({ connection: Connection.Connected, session });
-
      } catch (e: any) {
-
        console.debug("WalletConnect: connection failed.");
-
        store.set({ connection: Connection.Disconnected });
-

-
        // There seems to be no way to detect this "error" caused by the user
-
        // closing the modal dialog, besides matching on the message string.
-
        // Welcome to the wonderful ghetto that is WalletConnect.
-
        //
-
        // Since it's not really an error, we don't throw if this is what happened.
-
        if (e.message !== "User close QRCode Modal") {
-
          throw e;
-
        }
-
      }
-
    },
-

-
    updateBalance: (n: BigNumber) => {
-
      store.update((s: State) => {
-
        assert(s.connection === Connection.Connected);
-
        if (s.session.tokenBalance) {
-
          // If the token balance is loaded, we can update it, otherwise
-
          // we let it finish loading.
-
          s.session.tokenBalance = s.session.tokenBalance.add(n);
-
          saveMetamaskSession(s.session);
-
        }
-
        return s;
-
      });
-
    },
-

-
    refreshBalance: async (wallet: Wallet) => {
-
      const state = get(store);
-
      assert(state.connection === Connection.Connected);
-
      const addr = state.session.address;
-

-
      try {
-
        const tokenBalance: BigNumber = await wallet.token.balanceOf(addr);
-

-
        state.session.tokenBalance = tokenBalance;
-
        store.set(state);
-
      } catch (e) {
-
        console.error(e);
-
      }
-
    },
-

-
    setTxSigning: () => {
-
      store.update(s => {
-
        switch (s.connection) {
-
          case Connection.Connected:
-
            s.session.tx = { state: "signing" };
-
            return s;
-
          default:
-
            throw new Unreachable();
-
        }
-
      });
-
    },
-

-
    setTxPending: (tx: TransactionResponse) => {
-
      store.update(s => {
-
        switch (s.connection) {
-
          case Connection.Connected:
-
            assert(s.session.tx !== null);
-
            assert(s.session.tx.state === "signing");
-

-
            s.session.tx = { state: "pending", hash: tx.hash };
-
            return s;
-
          default:
-
            throw new Unreachable();
-
        }
-
      });
-
    },
-

-
    setTxConfirmed: (tx: TransactionReceipt) => {
-
      store.update(s => {
-
        switch (s.connection) {
-
          case Connection.Connected:
-
            assert(s.session.tx !== null);
-
            assert(s.session.tx.state === "pending");
-

-
            if (tx.status === 1) {
-
              s.session.tx = {
-
                state: "success",
-
                hash: s.session.tx.hash,
-
                blockHash: tx.blockHash,
-
                blockNumber: tx.blockNumber,
-
              };
-
            } else {
-
              s.session.tx = {
-
                state: "fail",
-
                hash: s.session.tx.hash,
-
                blockHash: tx.blockHash,
-
                blockNumber: tx.blockNumber,
-
                error: "Failed",
-
              };
-
            }
-
            return s;
-
          default:
-
            throw new Unreachable();
-
        }
-
      });
-
    },
-

-
    setChangedAccount: (address: string, signer: Signer) => {
-
      store.update(s => {
-
        switch (s.connection) {
-
          case Connection.Connected:
-
            // In case of locking Metamask the accountsChanged event returns undefined.
-
            // To prevent out of sync state, the wallet gets disconnected.
-
            if (address === undefined) {
-
              disconnectMetamask();
-
              return s;
-
            } else {
-
              s.session.address = address;
-
              s.session.signer = signer;
-
              // We only save the session to localStorage if we use a MetaMask signer
-
              // WalletConnect does their own session persistance.
-
              if (s.session.signerType === SignerType.MetaMask) {
-
                saveMetamaskSession(s.session);
-
              }
-
            }
-
            return s;
-
          default:
-
            return s;
-
        }
-
      });
-
    },
-
  };
-
};
-

-
// Initializes the session state on page load or hard refresh.
-
export const state = loadState({ connection: Connection.Disconnected });
-

-
export const session = derived(state, s => {
-
  if (s.connection === Connection.Connected) {
-
    return s.session;
-
  }
-
  return null;
-
});
-

-
window.ethereum?.on("chainChanged", () => {
-
  // We disconnect the wallet to avoid out of sync state.
-
  disconnectMetamask();
-
});
-

-
// Updates state when user changes accounts
-
window.ethereum?.on("accountsChanged", async ([address]: string) => {
-
  const s = get(session);
-
  // Only allow user to change accounts with Metamask if they are connected with Metamask.
-
  if (s?.signerType !== SignerType.MetaMask) {
-
    return;
-
  } else if (s.signer) {
-
    changeAccounts(address, s.signer);
-
  }
-
});
-

-
export async function changeAccounts(
-
  address: string,
-
  signer: Signer,
-
): Promise<void> {
-
  const wallet = await getWallet();
-
  state.setChangedAccount(address, signer);
-
  state.refreshBalance(wallet);
-
}
-

-
export async function approveSpender(
-
  spender: string,
-
  amount: BigNumber,
-
  wallet: Wallet,
-
): Promise<void> {
-
  assert(wallet.signer);
-

-
  const signer = wallet.signer;
-
  const addr = await signer.getAddress();
-

-
  const allowance = await wallet.token.allowance(addr, spender);
-

-
  if (allowance < amount) {
-
    const tx = await wallet.token.connect(signer).approve(spender, amount);
-
    await tx.wait();
-
  }
-
}
-

-
export function disconnectMetamask(): void {
-
  window.localStorage.removeItem("metamask");
-
  window.location.reload();
-
}
-

-
export function disconnectWallet(wallet: Wallet): void {
-
  if (wallet.walletConnect.client.connected) {
-
    wallet.walletConnect.client.killSession();
-
  }
-
  disconnectMetamask();
-
}
-

-
function saveMetamaskSession(session: Session): void {
-
  window.localStorage.setItem(
-
    "metamask",
-
    JSON.stringify({
-
      address: session.address,
-
      tokenBalance: null,
-
      tx: null,
-
      wallet: null,
-
    }),
-
  );
-
}
modified src/lib/utils.ts
@@ -1,46 +1,13 @@
-
import type { EnsProfile } from "@app/lib/registrar";
-
import type { Wallet } from "@app/lib/wallet";
import type { marked } from "marked";

import katex from "katex";
import md5 from "md5";
import twemojiModule from "twemoji";
-
import { BigNumber, ethers } from "ethers";
-
import { parseUnits } from "@ethersproject/units";

-
import * as cache from "@app/lib/cache";
import emojis from "@app/lib/emojis";
-
import { ProfileType } from "@app/lib/profile";
import { assert } from "@app/lib/error";
import { base } from "@app/lib/router";
import { config } from "@app/lib/config";
-
import { getAddress, getResolver } from "@app/lib/registrar";
-
import { getAvatar, getSeed, getRegistration } from "@app/lib/registrar";
-
import { getInfo } from "@app/lib/vesting";
-

-
export enum AddressType {
-
  Contract,
-
  Org,
-
  Vesting,
-
  EOA,
-
}
-

-
export interface Token {
-
  name: string;
-
  symbol: string;
-
  logo: string;
-
  decimals: number;
-
  balance: BigNumber;
-
}
-

-
export async function isReverseRecordSet(
-
  address: string,
-
  domain: string,
-
  wallet: Wallet,
-
): Promise<boolean> {
-
  const name = await lookupAddress(address, wallet);
-
  return name === domain;
-
}

export async function toClipboard(text: string): Promise<void> {
  return navigator.clipboard.writeText(text);
@@ -58,14 +25,6 @@ export function setOpenGraphMetaTag(
  });
}

-
export function toWei(amount: string): BigNumber {
-
  return parseUnits(amount);
-
}
-

-
export function isAddressEqual(left: string, right: string): boolean {
-
  return left.toLowerCase() === right.toLowerCase();
-
}
-

export function formatSeedAddress(
  id: string,
  host: string,
@@ -101,51 +60,15 @@ export function formatRadicleId(id: string): string {
  }
}

-
export function formatBalance(n: BigNumber, decimals?: number): string {
-
  return ethers.utils.commify(
-
    parseFloat(ethers.utils.formatUnits(n, decimals)).toFixed(2),
-
  );
-
}
-

-
// Returns a checksummed, shortened, without 0x prefix Ethereum address
-
export function formatAddress(input: string): string {
-
  const addr = ethers.utils.getAddress(input).replace(/^0x/, "");
-

-
  return (
-
    addr.substring(0, 4) + " – " + addr.substring(addr.length - 4, addr.length)
-
  );
-
}
-

-
// Returns a shortened Ethereum transaction hash
-
export function formatTx(input: string): string {
-
  return input.substring(0, 20) + "…";
-
}
-

export function formatCommit(oid: string): string {
  return oid.substring(0, 7);
}

-
export function formatProfile(input: string, wallet: Wallet): string {
-
  if (isAddress(input)) {
-
    return ethers.utils.getAddress(input);
-
  } else {
-
    return parseEnsLabel(input, wallet);
-
  }
-
}
-

export function capitalize(s: string): string {
  if (s === "") return s;
  return s[0].toUpperCase() + s.substring(1);
}

-
// Takes a domain name, eg. 'cloudhead.radicle.eth' and returns the label, eg. 'cloudhead'.
-
export function parseEnsLabel(name: string, wallet: Wallet): string {
-
  const domain = wallet.registrar.domain.replace(".", "\\.");
-
  const label = name.replace(new RegExp(`\\.${domain}$`), "");
-

-
  return label;
-
}
-

// Get the mime type of an image, given a file path.
// Returns `null` if unknown.
export function getImageMime(path: string): string | null {
@@ -265,37 +188,12 @@ export function isUrl(input: string): boolean {
  return /^https?:\/\//.test(input);
}

-
export function isENSName(input: string, wallet: Wallet): boolean {
-
  const domain = wallet.registrar.domain.replace(".", "\\.");
-
  const regEx = new RegExp(`^[a-zA-Z0-9]+.(${domain}|eth)$`);
-
  return regEx.test(input);
-
}
-

-
// Check whether the input is an checksummed or all lowercase Ethereum address.
-
export function isAddress(input: string): boolean {
-
  return ethers.utils.isAddress(input);
-
}
-

export function isFulfilled<T>(
  input: PromiseSettledResult<T>,
): input is PromiseFulfilledResult<T> {
  return input.status === "fulfilled";
}

-
// Get the explorer link of an address or tx, eg. Etherscan.
-
export function explorerLink(addrOrTx: string, wallet: Wallet): string {
-
  const type = isAddress(addrOrTx) ? "address" : "tx";
-
  if (wallet.network.name === "goerli") {
-
    return `https://goerli.etherscan.io/${type}/${addrOrTx}`;
-
  }
-
  return `https://etherscan.io/${type}/${addrOrTx}`;
-
}
-

-
// Format a name.
-
export function formatName(input: string, wallet: Wallet): string {
-
  return parseEnsLabel(input, wallet);
-
}
-

// Parse a Radicle Id.
export function parseRadicleId(id: string): string {
  if (window.HEARTWOOD) {
@@ -335,116 +233,6 @@ export function getSeedEmoji(seedHost: string): string {
  }
}

-
// Identify an address by checking whether it's a contract or an externally-owned address.
-
export async function identifyAddress(
-
  address: string,
-
  wallet: Wallet,
-
): Promise<AddressType> {
-
  const code = await getCode(address, wallet);
-
  const bytes = ethers.utils.arrayify(code);
-

-
  if (bytes.length > 0) {
-
    const info = await getInfo(address, wallet);
-
    if (info) {
-
      return AddressType.Vesting;
-
    }
-
    return AddressType.Contract;
-
  }
-
  return AddressType.EOA;
-
}
-

-
// Resolves an ENS profile or return null
-
export async function resolveEnsProfile(
-
  addressOrName: string,
-
  profileType: ProfileType,
-
  wallet: Wallet,
-
): Promise<EnsProfile | null> {
-
  const name = ethers.utils.isAddress(addressOrName)
-
    ? await lookupAddress(addressOrName, wallet)
-
    : addressOrName;
-

-
  if (name) {
-
    const resolver = await getResolver(name, wallet);
-
    if (!resolver) {
-
      return null;
-
    }
-

-
    if (profileType === ProfileType.Full) {
-
      const registration = await getRegistration(name, wallet, resolver);
-
      if (registration) {
-
        return registration.profile;
-
      }
-
    } else {
-
      const promises: [Promise<any>] = [getAvatar(name, wallet, resolver)];
-

-
      if (addressOrName === name) {
-
        promises.push(getAddress(resolver));
-
      } else {
-
        promises.push(Promise.resolve(addressOrName));
-
      }
-

-
      if (profileType === ProfileType.Project) {
-
        promises.push(getSeed(name, wallet, resolver));
-
      } else if (profileType === ProfileType.Minimal) {
-
        promises.push(Promise.resolve(null));
-
      }
-

-
      const project = await Promise.allSettled(promises);
-
      const [avatar, address, seed] =
-
        // Just checking for r.value equal null and casting to undefined,
-
        // since resolver functions return null.
-
        project.filter(isFulfilled).map(r => (r.value ? r.value : null));
-

-
      return {
-
        name,
-
        avatar,
-
        address,
-
        seed,
-
      };
-
    }
-
  }
-
  return null;
-
}
-

-
// Get token balances for an address.
-
export async function getTokens(
-
  address: string,
-
  wallet: Wallet,
-
): Promise<Array<Token>> {
-
  const userBalances = await getRpcMethod(
-
    "alchemy_getTokenBalances",
-
    [address, "DEFAULT_TOKENS"],
-
    wallet,
-
  );
-
  const balances = userBalances.tokenBalances
-
    .filter((token: any) => {
-
      // alchemy_getTokenBalances sometimes returns 0x and this does not work well with ethers.BigNumber
-
      if (token.tokenBalance !== "0x") {
-
        if (!BigNumber.from(token.tokenBalance).isZero()) {
-
          return token;
-
        }
-
      }
-
    })
-
    .map(async (token: any) => {
-
      const tokenMetaData = await getRpcMethod(
-
        "alchemy_getTokenMetadata",
-
        [token.contractAddress],
-
        wallet,
-
      );
-
      return { ...tokenMetaData, balance: BigNumber.from(token.tokenBalance) };
-
    });
-

-
  return Promise.all(balances);
-
}
-

-
export const getRpcMethod = cache.cached(
-
  async (method: string, props: string[], wallet: Wallet) => {
-
    return await wallet.provider.send(method, props);
-
  },
-
  (method, props) => JSON.stringify([method, props]),
-
  { ttl: 2 * 60 * 1000, max: 1000 },
-
);
-

// Check whether the given path has a markdown file extension.
export function isMarkdownPath(path: string): boolean {
  return /\.(md|mkd|markdown)$/i.test(path);
@@ -472,22 +260,6 @@ export function gravatarURL(email: string): string {
  return `https://www.gravatar.com/avatar/${hash}`;
}

-
export const getCode = cache.cached(
-
  async (address: string, wallet: Wallet) => {
-
    return await wallet.provider.getCode(address);
-
  },
-
  address => address,
-
  { max: 1000 },
-
);
-

-
export const lookupAddress = cache.cached(
-
  async (address: string, wallet: Wallet) => {
-
    return await wallet.provider.lookupAddress(address);
-
  },
-
  address => address,
-
  { max: 1000 },
-
);
-

export const unreachable = (value: never): never => {
  throw new Error(`Unreachable code: ${value}`);
};
deleted src/lib/vesting.ts
@@ -1,179 +0,0 @@
-
import type { Wallet } from "@app/lib/wallet";
-
import type { TransactionResponse } from "@ethersproject/abstract-provider";
-

-
import { BigNumber, ethers } from "ethers";
-
import { writable } from "svelte/store";
-

-
import * as cache from "@app/lib/cache";
-
import * as modal from "@app/lib/modal";
-
import * as session from "@app/lib/session";
-
import * as utils from "@app/lib/utils";
-
import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json";
-

-
import ErrorModal from "@app/components/ErrorModal.svelte";
-

-
export interface VestingInfo {
-
  token: string;
-
  symbol: string;
-
  beneficiary: string;
-
  totalVesting: string;
-
  withdrawableBalance: string;
-
  withdrawn: string;
-
  cliffPeriod: string;
-
  vestingStartTime: string;
-
  vestingPeriod: string;
-
}
-

-
export type VestingState =
-
  | { type: "idle" }
-
  | { type: "loading" }
-
  | { type: "withdrawingSign" }
-
  | { type: "withdrawing"; tx: TransactionResponse }
-
  | { type: "withdrawn" };
-

-
export const state = writable<VestingState>({ type: "idle" });
-

-
export async function withdrawVested(
-
  address: string,
-
  wallet: Wallet,
-
): Promise<void> {
-
  if (!wallet.signer) {
-
    modal.show({
-
      component: ErrorModal,
-
      props: {
-
        title: "Withdraw failed",
-
        caption: "No signer available. Is your wallet connected?",
-
      },
-
    });
-
    return;
-
  }
-

-
  const contract = new ethers.Contract(
-
    address,
-
    ethereumContractAbis.vesting,
-
    wallet.provider,
-
  );
-
  const signer = wallet.signer;
-

-
  state.set({ type: "withdrawingSign" });
-

-
  try {
-
    const tx: TransactionResponse = await contract
-
      .connect(signer)
-
      .withdrawVested();
-

-
    state.set({ type: "withdrawing", tx });
-
    await tx.wait();
-
  } catch (e) {
-
    handleEtherErrorState(e, "Unknown error, check the dev console");
-
    return;
-
  }
-
  session.state.refreshBalance(wallet);
-
  state.set({ type: "withdrawn" });
-
}
-

-
export const getInfo = cache.cached(
-
  async (address: string, wallet: Wallet): Promise<VestingInfo | undefined> => {
-
    const contract = getVestingContract(address, wallet);
-

-
    let vestingInfo:
-
      | [
-
          string,
-
          string,
-
          BigNumber,
-
          BigNumber,
-
          BigNumber,
-
          string,
-
          string,
-
          string,
-
        ]
-
      | undefined = undefined;
-
    let token: string | undefined = undefined;
-

-
    try {
-
      token = await contract.token();
-
      if (!token) {
-
        return undefined;
-
      }
-

-
      const tokenContract = new ethers.Contract(
-
        token,
-
        ethereumContractAbis.token,
-
        wallet.provider,
-
      );
-

-
      vestingInfo = await Promise.all([
-
        tokenContract.symbol(),
-
        contract.beneficiary(),
-
        contract.withdrawableBalance(),
-
        contract.withdrawn(),
-
        contract.totalVestingAmount(),
-
        contract.vestingStartTime(),
-
        contract.vestingPeriod(),
-
        contract.cliffPeriod(),
-
      ]);
-
    } catch (e) {
-
      console.warn(e);
-
      return undefined;
-
    }
-

-
    const [
-
      symbol,
-
      beneficiary,
-
      withdrawable,
-
      withdrawn,
-
      total,
-
      vestingStartTime,
-
      vestingPeriod,
-
      cliffPeriod,
-
    ] = vestingInfo;
-

-
    return {
-
      token,
-
      symbol,
-
      beneficiary,
-
      withdrawableBalance: utils.formatBalance(withdrawable),
-
      withdrawn: utils.formatBalance(withdrawn),
-
      totalVesting: utils.formatBalance(total),
-
      vestingStartTime,
-
      vestingPeriod,
-
      cliffPeriod,
-
    };
-
  },
-
  address => address,
-
  { max: 1000 },
-
);
-

-
export function parseVestingPeriods(...timestamps: string[]): string {
-
  const sum = timestamps
-
    .map(timestamp => parseInt(timestamp))
-
    .reduce((prev, curr) => prev + curr, 0);
-
  return new Date(sum * 1000).toDateString();
-
}
-

-
export function getVestingContract(address: string, wallet: Wallet) {
-
  return new ethers.Contract(
-
    address,
-
    ethereumContractAbis.vesting,
-
    wallet.provider,
-
  );
-
}
-

-
export function handleEtherErrorState(e: unknown, message: string): void {
-
  const error =
-
    typeof e === "object" &&
-
    e !== null &&
-
    "reason" in e &&
-
    typeof e.reason === "string"
-
      ? e.reason
-
      : message;
-

-
  modal.show({
-
    component: ErrorModal,
-
    props: {
-
      title: "Withdraw failed",
-
      error,
-
    },
-
  });
-
  console.warn(e);
-
}
deleted src/lib/wallet.ts
@@ -1,262 +0,0 @@
-
import type { TypedDataSigner } from "@ethersproject/abstract-signer";
-
import type { Writable } from "svelte/store";
-

-
import WalletConnect from "@walletconnect/client";
-
import { ethers } from "ethers";
-
import { get, writable } from "svelte/store";
-

-
import ethereumContractAbis from "@app/lib/ethereum/contractAbis.json";
-
import goerli from "@app/lib/ethereum/networks/goerli.json";
-
import homestead from "@app/lib/ethereum/networks/homestead.json";
-
import { WalletConnectSigner } from "@app/lib/walletConnectSigner";
-
import { capitalize } from "@app/lib/utils";
-
import { config } from "@app/lib/config";
-

-
interface NetworkConfig {
-
  name: string;
-
  chainId: number;
-
  registrar: {
-
    domain: string;
-
    address: string;
-
  };
-
  radToken: {
-
    address: string;
-
    faucet?: string;
-
  };
-
  reverseRegistrar: {
-
    address: string;
-
  };
-
  alchemy: { key: string };
-
}
-

-
export type WalletConnectState =
-
  | { state: "close" }
-
  | { state: "open"; uri: string; onClose: any };
-

-
export class Wallet {
-
  network: { name: string; chainId: number };
-
  registrar: { address: string; domain: string };
-
  radToken: { address: string; faucet?: string };
-
  reverseRegistrar: { address: string };
-
  provider: ethers.providers.JsonRpcProvider;
-
  signer: (ethers.Signer & TypedDataSigner) | WalletConnectSigner | null;
-
  walletConnect: {
-
    client: WalletConnect;
-
    bridge: string;
-
    signer: WalletConnectSigner;
-
    state: Writable<WalletConnectState>;
-
  };
-
  metamask:
-
    | {
-
        connected: true;
-
        signer: ethers.Signer & TypedDataSigner;
-
        session: { address: string };
-
      }
-
    | {
-
        connected: false;
-
        signer: (ethers.Signer & TypedDataSigner) | null;
-
      };
-
  token: ethers.Contract;
-

-
  constructor(
-
    network: NetworkConfig,
-
    provider: ethers.providers.JsonRpcProvider,
-
    metamaskSigner: (ethers.Signer & TypedDataSigner) | null,
-
  ) {
-
    const walletConnectState = writable<WalletConnectState>({ state: "close" });
-
    const wc = Wallet.initializeWalletConnect(
-
      config.walletConnect.bridge,
-
      walletConnectState,
-
      provider,
-
    );
-
    const metamaskSession = window.localStorage.getItem("metamask");
-
    const metamask = metamaskSession ? JSON.parse(metamaskSession) : null;
-

-
    this.network = network;
-
    this.metamask =
-
      metamask && metamaskSigner
-
        ? {
-
            connected: true,
-
            session: { address: metamask["address"] },
-
            signer: metamaskSigner,
-
          }
-
        : {
-
            connected: false,
-
            signer: metamaskSigner,
-
          };
-
    this.walletConnect = {
-
      bridge: config.walletConnect.bridge,
-
      client: wc.connector,
-
      signer: wc.signer,
-
      state: walletConnectState,
-
    };
-
    this.registrar = network.registrar;
-
    this.radToken = network.radToken;
-
    this.reverseRegistrar = network.reverseRegistrar;
-
    this.provider = provider;
-
    this.signer = null;
-
    this.token = new ethers.Contract(
-
      this.radToken.address,
-
      ethereumContractAbis.token,
-
      this.provider,
-
    );
-
  }
-

-
  changeNetwork(chainId: number): void {
-
    this.network = ethers.providers.getNetwork(chainId);
-
  }
-

-
  setSigner(
-
    signer: (ethers.Signer & TypedDataSigner) | WalletConnectSigner,
-
  ): void {
-
    this.signer = signer;
-
  }
-

-
  getWalletConnectSigner(): WalletConnectSigner {
-
    if (this.walletConnect.client.connected) {
-
      this.setSigner(this.walletConnect.signer);
-
      return this.walletConnect.signer;
-
    }
-
    const wc = Wallet.initializeWalletConnect(
-
      this.walletConnect.bridge,
-
      this.walletConnect.state,
-
      this.provider,
-
    );
-
    this.walletConnect.client = wc.connector;
-
    this.walletConnect.signer = wc.signer;
-
    this.setSigner(wc.signer);
-

-
    return wc.signer;
-
  }
-

-
  static initializeWalletConnect(
-
    bridge: string,
-
    state: Writable<WalletConnectState>,
-
    provider: ethers.providers.JsonRpcProvider,
-
  ): {
-
    connector: WalletConnect;
-
    signer: WalletConnectSigner;
-
  } {
-
    const walletConnect = new WalletConnect({
-
      bridge,
-
      qrcodeModal: {
-
        open: (uri: string, onClose) => {
-
          state.set({ state: "open", uri, onClose });
-
        },
-
        close: () => {
-
          state.set({ state: "close" });
-
        },
-
      },
-
    });
-
    walletConnect.on("modal_closed", () => {
-
      state.set({ state: "close" });
-
    });
-
    walletConnect.on("disconnect", () => {
-
      const wcs = get(state);
-
      if (wcs.state === "open") {
-
        wcs.onClose();
-
      }
-
    });
-

-
    // Behold, we set this private class variable here because WalletConnect doesn't
-
    // give us any other way to set it :'(
-
    //
-
    // The default is to use the favicon, which doesn't work, given that it is
-
    // designed for browsers and not mobile apps which often show a much bigger
-
    // icon, resulting in a blurry image.
-
    (walletConnect as any)._clientMeta.icons = [
-
      `${window.location.protocol}//${window.location.host}/logo.png`,
-
    ];
-

-
    const walletConnectSigner = new WalletConnectSigner(
-
      walletConnect,
-
      provider,
-
    );
-

-
    return {
-
      connector: walletConnect,
-
      signer: walletConnectSigner,
-
    };
-
  }
-
}
-

-
export function isMetamaskInstalled(): boolean {
-
  const { ethereum } = window;
-
  return Boolean(ethereum && ethereum.isMetaMask);
-
}
-

-
function getProvider(
-
  networkConfig: NetworkConfig,
-
  metamask: ethers.providers.JsonRpcProvider | null,
-
): ethers.providers.JsonRpcProvider {
-
  if (metamask) {
-
    return metamask;
-
  } else if (
-
    import.meta.env.PROD &&
-
    window.location.host !== "localhost:4173"
-
  ) {
-
    return new ethers.providers.AlchemyWebSocketProvider(
-
      networkConfig.name,
-
      networkConfig.alchemy.key,
-
    );
-
  }
-
  // Run the production smoke test with the ethers provider,
-
  // because we block requests from localhost on Alchemy,
-
  // which in turn throws an exception.
-
  else if (import.meta.env.DEV || window.location.host === "localhost:4173") {
-
    // The ethers defaultProvider doesn't include a `send` method, which breaks the `utils.getTokens` fn.
-
    // Since Metamask nor WalletConnect provide an `alchemy_getTokenBalances` nor `alchemy_getTokenMetadata` endpoint,
-
    // we can rely on not using `config.provider.send`.
-
    return ethers.providers.getDefaultProvider() as ethers.providers.JsonRpcProvider;
-
  } else {
-
    throw new Error("No Web3 provider available.");
-
  }
-
}
-

-
// Checks if the promise metamask.ready returns the network, else timesout after 4 seconds.
-
function checkMetaMask(
-
  metamask: ethers.providers.Web3Provider,
-
): Promise<ethers.providers.Network | null> {
-
  return new Promise(resolve => {
-
    setTimeout(() => {
-
      resolve(null);
-
    }, 4000);
-
    metamask.ready.then(network => resolve(network));
-
  });
-
}
-

-
export async function getWallet(): Promise<Wallet> {
-
  const metamask = isMetamaskInstalled()
-
    ? new ethers.providers.Web3Provider(window.ethereum)
-
    : null;
-
  const metamaskSigner = metamask?.getSigner() || null;
-

-
  let selectedNetwork: NetworkConfig;
-

-
  if (metamask) {
-
    const ready = await checkMetaMask(metamask);
-
    if (ready) {
-
      if (ready.name === "homestead") {
-
        selectedNetwork = homestead;
-
      } else if (ready.name === "goerli") {
-
        selectedNetwork = goerli;
-
      } else {
-
        throw new Error(
-
          `${capitalize(
-
            ready.name,
-
          )} is not supported. Connect to Homestead or Goerli instead.`,
-
        );
-
      }
-
    } else {
-
      throw new Error("Metamask was not ready after 4 seconds.");
-
    }
-
  } else {
-
    // Fall back to homestead.
-
    selectedNetwork = homestead;
-
  }
-

-
  const provider = getProvider(selectedNetwork, metamask);
-
  const cfg = new Wallet(selectedNetwork, provider, metamaskSigner);
-

-
  return cfg;
-
}
deleted src/lib/walletConnectSigner.ts
@@ -1,158 +0,0 @@
-
import type WalletConnect from "@walletconnect/client";
-
import type { Deferrable } from "@ethersproject/properties";
-
import type {
-
  TransactionRequest,
-
  TransactionResponse,
-
} from "@ethersproject/abstract-provider";
-
import type {
-
  TypedDataDomain,
-
  TypedDataField,
-
} from "@ethersproject/abstract-signer";
-

-
import * as ethers from "ethers";
-
import * as ethersBytes from "@ethersproject/bytes";
-
import { _TypedDataEncoder } from "ethers/lib/utils";
-
import { resolveProperties } from "@ethersproject/properties";
-

-
export class WalletConnectSigner extends ethers.Signer {
-
  public walletConnect: WalletConnect;
-
  public readonly provider: ethers.providers.JsonRpcProvider;
-

-
  constructor(
-
    walletConnect: WalletConnect,
-
    provider: ethers.providers.JsonRpcProvider,
-
  ) {
-
    super();
-

-
    this.provider = provider;
-
    this.walletConnect = walletConnect;
-
  }
-

-
  async getAddress(): Promise<string> {
-
    const accountAddress = this.walletConnect.accounts[0];
-
    if (!accountAddress) {
-
      throw new Error(
-
        "The connected wallet has no accounts or there is a connection problem",
-
      );
-
    }
-
    return ethers.utils.getAddress(accountAddress);
-
  }
-

-
  async _signTypedData(
-
    domain: TypedDataDomain,
-
    types: Record<string, Array<TypedDataField>>,
-
    value: Record<string, any>,
-
  ): Promise<string> {
-
    // Populate any ENS names (in-place)
-
    const populated = await _TypedDataEncoder.resolveNames(
-
      domain,
-
      types,
-
      value,
-
      async (name: string) => {
-
        const address = await this.provider.resolveName(name);
-
        if (address === null) {
-
          throw Error("resolver or addr is not configured for ENS name");
-
        }
-
        return address;
-
      },
-
    );
-

-
    const address = await this.getAddress();
-
    const signature = await this.walletConnect.signTypedData([
-
      address.toLowerCase(),
-
      JSON.stringify(
-
        _TypedDataEncoder.getPayload(populated.domain, types, populated.value),
-
      ),
-
    ]);
-
    return signature;
-
  }
-

-
  async signMessage(message: ethers.Bytes | string): Promise<string> {
-
    const prefix = ethers.utils.toUtf8Bytes(
-
      `\x19Ethereum Signed Message:\n${message.length}`,
-
    );
-
    const data =
-
      typeof message === "string" ? ethers.utils.toUtf8Bytes(message) : message;
-

-
    const msg = ethers.utils.concat([prefix, data]);
-
    const address = await this.getAddress();
-
    const keccakMessage = ethers.utils.keccak256(msg);
-
    const signature = await this.walletConnect.signMessage([
-
      address,
-
      keccakMessage,
-
    ]);
-

-
    return signature;
-
  }
-

-
  async sendTransaction(
-
    transaction: Deferrable<TransactionRequest>,
-
  ): Promise<TransactionResponse> {
-
    const tx = await resolveProperties(transaction);
-
    const from = tx.from || (await this.getAddress());
-

-
    const txHash = await this.walletConnect.sendTransaction({
-
      from,
-
      to: tx.to,
-
      value: maybeBigNumberToString(tx.value),
-
      data: bytesLikeToString(tx.data),
-
    });
-
    return <TransactionResponse>{
-
      hash: txHash,
-
      nonce: tx.nonce,
-
      gasLimit: tx.gasLimit,
-
      gasPrice: tx.gasPrice,
-
      data: bytesLikeToString(tx.data) || "",
-
      value: tx.value,
-
      chainId: tx.chainId,
-
      confirmations: 0,
-
      from: from,
-
      wait: (confirmations?: number) => {
-
        return this.provider?.waitForTransaction(txHash, confirmations);
-
      },
-
    };
-
  }
-

-
  async signTransaction(
-
    transaction: Deferrable<TransactionRequest>,
-
  ): Promise<string> {
-
    const tx = await resolveProperties(transaction);
-
    const from = tx.from || (await this.getAddress());
-
    const nonce = await this.provider.getTransactionCount(from);
-

-
    const signedTx = await this.walletConnect.signTransaction({
-
      from,
-
      to: tx.to,
-
      value: maybeBigNumberToString(tx.value || 0),
-
      gasLimit: maybeBigNumberToString(tx.gasLimit || 200 * 1000),
-
      gasPrice: maybeBigNumberToString(tx.gasPrice || 0),
-
      nonce,
-
      data: bytesLikeToString(tx.data),
-
    });
-
    return signedTx;
-
  }
-

-
  connect(): ethers.Signer {
-
    throw new Error("WalletConnectSigner.connect should never be called");
-
  }
-
}
-

-
function maybeBigNumberToString(
-
  bn: ethers.BigNumberish | undefined,
-
): string | undefined {
-
  if (bn === undefined) {
-
    return undefined;
-
  } else {
-
    return ethers.BigNumber.from(bn).toString();
-
  }
-
}
-

-
function bytesLikeToString(
-
  bytes: ethersBytes.BytesLike | undefined,
-
): string | undefined {
-
  if (bytes === undefined) {
-
    return undefined;
-
  } else {
-
    return ethersBytes.hexlify(bytes);
-
  }
-
}
deleted src/views/faucet/Faucet.svelte
@@ -1,174 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { formatEther } from "@ethersproject/units";
-

-
  import * as modal from "@app/lib/modal";
-
  import WithdrawModal from "@app/views/faucet/WithdrawModal.svelte";
-
  import Button from "@app/components/Button.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";
-
  import {
-
    calculateTimeLock,
-
    getMaxWithdrawAmount,
-
    lastWithdrawalByUser,
-
  } from "@app/lib/faucet";
-
  import { session } from "@app/lib/session";
-
  import { setOpenGraphMetaTag, toWei, capitalize } from "@app/lib/utils";
-

-
  export let wallet: Wallet;
-

-
  let amount: string = "";
-
  let loading: boolean = false;
-
  let validationMessage: string | undefined = undefined;
-
  let valid: boolean = false;
-

-
  setOpenGraphMetaTag([
-
    { prop: "og:title", content: "Radicle Faucet" },
-
    { prop: "og:description", content: "Goerli Testnet Faucet" },
-
    { prop: "og:url", content: window.location.href },
-
  ]);
-

-
  async function withdraw(amount: string) {
-
    if (!valid || !$session) {
-
      return;
-
    }
-

-
    loading = true;
-
    try {
-
      const currentTime = new Date().getTime();
-
      const timelock = await calculateTimeLock(amount, $session.signer, wallet);
-
      const lastWithdrawal = await lastWithdrawalByUser(
-
        $session.signer,
-
        wallet,
-
      );
-
      const maxWithdrawAmount = await getMaxWithdrawAmount(
-
        $session.signer,
-
        wallet,
-
      );
-

-
      if (toWei(amount).gt(maxWithdrawAmount)) {
-
        validationMessage = `Reduce amount, max withdrawal is ${formatEther(
-
          maxWithdrawAmount,
-
        )}.`;
-
        return;
-
      }
-

-
      // Converting a 10 digit to 13 digit timestamp by multiplying by 1000
-
      // since JS doesn't display a correct Date string when passing a 10 digit
-
      // timestamp.
-
      const nextAvailableWithdraw = lastWithdrawal.add(timelock).mul(1000);
-
      if (nextAvailableWithdraw.gt(currentTime)) {
-
        validationMessage = `Not ready to withdraw, return after ${new Date(
-
          nextAvailableWithdraw.toNumber(),
-
        ).toLocaleString("en-GB")}`;
-
        return;
-
      }
-

-
      modal.show({ component: WithdrawModal, props: { amount, wallet } });
-
    } catch (error) {
-
      validationMessage = "There was an error, check the dev console.";
-
      console.error(error);
-
    } finally {
-
      loading = false;
-
    }
-
  }
-

-
  function validate(amount: string) {
-
    if (amount === "") {
-
      return { valid: false };
-
    }
-

-
    if (isNaN(Number(amount)) || Number(amount) <= 0) {
-
      return {
-
        valid: false,
-
        validationMessage: "Please enter a positive number.",
-
      };
-
    }
-

-
    return { valid: true };
-
  }
-

-
  $: ({ valid, validationMessage } = validate(amount));
-
</script>
-

-
<style>
-
  main {
-
    display: flex;
-
    flex-direction: column;
-
    gap: 1.5rem;
-
    height: 100%;
-
    justify-content: center;
-
    padding-bottom: 24vh;
-
    padding-top: 5rem;
-
    width: 28rem;
-
  }
-
  .title {
-
    color: var(--color-secondary);
-
    font-size: var(--font-size-medium);
-
  }
-
  .subtitle {
-
    color: var(--color-secondary);
-
  }
-
  .form {
-
    display: flex;
-
    gap: 1rem;
-
  }
-
</style>
-

-
<svelte:head>
-
  <title>Radicle &ndash; Faucet</title>
-
</svelte:head>
-

-
<main>
-
  <div class="title">
-
    Obtain RAD tokens on <span class="txt-bold">
-
      {capitalize(wallet.network.name)}
-
    </span>
-
  </div>
-

-
  {#if wallet.network.name === "homestead"}
-
    <div class="subtitle">
-
      To get RAD tokens on <span class="txt-bold">
-
        {capitalize(wallet.network.name)},
-
      </span>
-
      please
-
      <br />
-
      check
-
      <a
-
        href="https://docs.radicle.xyz/get-involved/obtain-rad"
-
        class="txt-link">
-
        popular exchanges
-
      </a>
-
      &#8203;.
-
    </div>
-
  {:else if !$session}
-
    <div class="subtitle">
-
      To get RAD tokens on <span class="txt-bold">
-
        {capitalize(wallet.network.name)}
-
      </span>
-
      &#8203;,
-
      <br />
-
      please connect your wallet.
-
    </div>
-
  {:else}
-
    <div class="form">
-
      <TextInput
-
        autofocus
-
        placeholder="Enter amount to withdraw"
-
        {validationMessage}
-
        on:submit={() => {
-
          withdraw(amount);
-
        }}
-
        bind:value={amount}
-
        {valid}
-
        {loading} />
-

-
      <Button
-
        variant="primary"
-
        on:click={() => withdraw(amount)}
-
        disabled={!valid || loading}>
-
        Withdraw
-
      </Button>
-
    </div>
-
  {/if}
-
</main>
deleted src/views/faucet/WithdrawModal.svelte
@@ -1,85 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { onMount } from "svelte";
-

-
  import * as modal from "@app/lib/modal";
-
  import * as utils from "@app/lib/utils";
-
  import { session } from "@app/lib/session";
-
  import { withdraw } from "@app/lib/faucet";
-

-
  import Loading from "@app/components/Loading.svelte";
-
  import Modal from "@app/components/Modal.svelte";
-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-

-
  export let wallet: Wallet;
-
  export let amount: string;
-

-
  let state: "initial" | "signing" | "pending" | "success" = "initial";
-

-
  onMount(async () => {
-
    modal.disableHide();
-
    try {
-
      if ($session) {
-
        state = "signing";
-
        const tx = await withdraw(amount, $session.signer, wallet);
-
        state = "pending";
-
        await tx.wait();
-
        state = "success";
-
        modal.enableHide();
-
      } else {
-
        modal.enableHide();
-
        modal.hide();
-
      }
-
    } catch (error: unknown) {
-
      let message: string | undefined;
-

-
      if (error instanceof Error) {
-
        message = error.message;
-
      } else {
-
        message = "Unknown error. Check dev console for details.";
-
        console.error(error);
-
      }
-

-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Transaction failed",
-
          error: message,
-
        },
-
      });
-
    }
-
  });
-
</script>
-

-
<Modal
-
  emoji={state === "success" ? "🎉" : "🌐"}
-
  title="Withdraw"
-
  closeAction={state === "success" ? { name: "Done" } : false}>
-
  <span slot="subtitle">
-
    {#if state === "signing"}
-
      {#if $session?.address}
-
        Send {amount} RAD to {utils.formatAddress($session.address)}.
-
        <br />
-
      {/if}
-
      Please confirm the transaction in your wallet.
-
    {:else if state === "pending"}
-
      Waiting for transaction to be processed…
-
    {/if}
-
  </span>
-

-
  <span slot="body">
-
    {#if state === "success"}
-
      {amount} RAD has been sent to
-
      {#if $session?.address}
-
        <span class="txt-highlight">
-
          {utils.formatAddress($session.address)}.
-
        </span>
-
      {/if}
-
    {:else}
-
      <div style:margin-top="2rem">
-
        <Loading noDelay small center />
-
      </div>
-
    {/if}
-
  </span>
-
</Modal>
modified src/views/home/Index.svelte
@@ -34,7 +34,6 @@
        id: project.id,
        peer: undefined,
        seed: seed.host,
-
        profile: undefined,
        revision: project.head ?? undefined,
      },
    });
deleted src/views/profiles/Profile.svelte
@@ -1,432 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-
  import type { Seed, Stats } from "@app/lib/seed";
-
  import type { ProjectInfo } from "@app/lib/project";
-
  import type { VestingInfo } from "@app/lib/vesting";
-

-
  import * as modal from "@app/lib/modal";
-
  import * as utils from "@app/lib/utils";
-
  import Address from "@app/components/Address.svelte";
-
  import Async from "@app/components/Async.svelte";
-
  import Avatar from "@app/components/Avatar.svelte";
-
  import Badge from "@app/components/Badge.svelte";
-
  import Button from "@app/components/Button.svelte";
-
  import Error from "@app/components/Error.svelte";
-
  import Icon from "@app/components/Icon.svelte";
-
  import Link from "@app/components/Link.svelte";
-
  import Loading from "@app/components/Loading.svelte";
-
  import NotFound from "@app/components/NotFound.svelte";
-
  import Projects from "@app/views/seeds/View/Projects.svelte";
-
  import RadicleId from "@app/components/RadicleId.svelte";
-
  import SeedAddress from "@app/components/SeedAddress.svelte";
-
  import SetNameModal from "@app/views/profiles/SetNameModal.svelte";
-
  import WithdrawModal from "@app/views/vesting/WithdrawModal.svelte";
-
  import { MissingReverseRecord, NotFoundError } from "@app/lib/error";
-
  import { User, Profile, ProfileType } from "@app/lib/profile";
-
  import { defaultNodePort } from "@app/lib/seed";
-
  import {
-
    getInfo,
-
    getVestingContract,
-
    handleEtherErrorState,
-
    parseVestingPeriods,
-
  } from "@app/lib/vesting";
-
  import { onMount } from "svelte";
-
  import { session } from "@app/lib/session";
-

-
  export let wallet: Wallet;
-
  export let addressOrName: string;
-

-
  let vestingInfo: VestingInfo | undefined = undefined;
-
  const getProjectsAndStats = async (
-
    seed: Seed,
-
    id?: string,
-
  ): Promise<{
-
    stats: Stats;
-
    projects: ProjectInfo[];
-
  }> => {
-
    const stats = await seed.getStats();
-
    const projects = await seed.getProjects(10, id);
-
    return { stats, projects };
-
  };
-

-
  // Refresh vestingInfo and close modal if addressOrName changes
-
  $: {
-
    vestingInfo = undefined;
-
    modal.hide();
-
    getInfo(addressOrName, wallet)
-
      .then(info => {
-
        vestingInfo = info;
-
      })
-
      .catch(() => {
-
        console.warn("Not able to get vesting contract info");
-
      });
-
  }
-

-
  onMount(async () => {
-
    const addressType = await utils.identifyAddress(addressOrName, wallet);
-
    if (addressType === utils.AddressType.Contract) {
-
      try {
-
        vestingInfo = await getInfo(addressOrName, wallet);
-
      } catch (e) {
-
        handleEtherErrorState(e, "Not able to get vesting contract info");
-
      }
-
    }
-
  });
-

-
  const vestingContract = getVestingContract(addressOrName, wallet);
-

-
  wallet.provider.on("block", async () => {
-
    if (vestingInfo) {
-
      try {
-
        const updatedAmounts = await Promise.all([
-
          vestingContract.withdrawableBalance(),
-
          vestingContract.withdrawn(),
-
        ]);
-
        vestingInfo.withdrawableBalance = utils.formatBalance(
-
          updatedAmounts[0],
-
        );
-
        vestingInfo.withdrawn = utils.formatBalance(updatedAmounts[1]);
-
      } catch (e) {
-
        handleEtherErrorState(e, "Not able to update the balance");
-
      }
-
    }
-
  });
-

-
  $: isUserAuthorized = (address: string): boolean | null => {
-
    return $session && utils.isAddressEqual(address, $session.address);
-
  };
-
</script>
-

-
<style>
-
  main {
-
    padding: 5rem 0;
-
    width: 720px;
-
  }
-
  main > header {
-
    display: flex;
-
    align-items: center;
-
    justify-content: left;
-
    margin-bottom: 2rem;
-
  }
-
  main > header > * {
-
    margin: 0 1rem 0 0;
-
  }
-
  .info {
-
    display: flex;
-
    flex-direction: column;
-
    justify-content: center;
-
    align-items: left;
-
  }
-
  .info a {
-
    border: none;
-
  }
-
  .fields {
-
    display: grid;
-
    grid-template-columns: max-content 4fr 2fr;
-
    gap: 1rem 2rem;
-
    margin-bottom: 1rem;
-
  }
-
  .fields > div {
-
    place-self: center start;
-
    height: 2rem;
-
    line-height: 2rem;
-
  }
-
  .avatar {
-
    width: 64px;
-
    height: 64px;
-
  }
-
  .title {
-
    display: flex;
-
    align-items: center;
-
    gap: 0.5rem;
-
  }
-
  .links {
-
    display: flex;
-
    align-items: center;
-
    justify-content: left;
-
  }
-
  .overflow-text {
-
    width: 100%;
-
    overflow: hidden;
-
    text-overflow: ellipsis;
-
    white-space: nowrap;
-
  }
-
  .url {
-
    display: flex; /* Ensures correct vertical positioning of icons */
-
    margin-right: 0.5rem;
-
    height: 1.6rem;
-
    align-items: center;
-
  }
-
  @media (max-width: 720px) {
-
    main {
-
      width: 100%;
-
      padding: 1.5rem;
-
    }
-
    .fields {
-
      grid-template-columns: max-content auto;
-
    }
-
  }
-
  .container {
-
    height: 100%;
-
    display: flex;
-
    align-items: center;
-
    justify-content: center;
-
  }
-
</style>
-

-
<svelte:head>
-
  <title>{addressOrName}</title>
-
</svelte:head>
-

-
{#await Profile.get(addressOrName, ProfileType.Full, wallet)}
-
  <div class="layout-centered">
-
    <Loading center />
-
  </div>
-
{:then profile}
-
  <main>
-
    <header>
-
      <div class="avatar">
-
        <Avatar
-
          source={profile.avatar ?? profile.address}
-
          title={profile.address} />
-
      </div>
-
      <div class="info">
-
        <span class="title txt-title">
-
          <span class="txt-bold">
-
            {profile.name
-
              ? utils.formatName(profile.name, wallet)
-
              : utils.formatAddress(profile.address)}
-
          </span>
-
          {#if profile.name && profile.org}
-
            <Badge variant="foreground">org</Badge>
-
          {/if}
-
        </span>
-
        <div class="links">
-
          {#if profile.url}
-
            <a class="url" href={profile.url}>
-
              <span class="layout-mobile">
-
                <Icon name="url" />
-
              </span>
-
              <span class="layout-desktop" style="margin-right: 0.3rem;">
-
                {profile.url}
-
              </span>
-
            </a>
-
          {/if}
-
          {#if profile.twitter}
-
            <a class="url" href="https://twitter.com/{profile.twitter}">
-
              <Icon name="twitter" />
-
            </a>
-
          {/if}
-
          {#if profile.github}
-
            <a class="url" href="https://github.com/{profile.github}">
-
              <Icon name="github" />
-
            </a>
-
          {/if}
-
          {#if utils.isAddress(profile.address)}
-
            <a
-
              class="url"
-
              title="Lookup address on Etherscan"
-
              href={utils.explorerLink(profile.address, wallet)}>
-
              <Icon name="etherscan" />
-
            </a>
-
          {/if}
-
        </div>
-
      </div>
-
    </header>
-

-
    <div class="fields">
-
      <!-- ID -->
-
      {#if profile.id}
-
        <div class="txt-highlight">ID</div>
-
        <RadicleId id={profile.id} />
-
      {/if}
-
      <!-- Seed Address -->
-
      {#if profile.seed && profile.seed.valid}
-
        <div class="txt-highlight">Seed</div>
-
        <SeedAddress seed={profile.seed} port={defaultNodePort} />
-
      {/if}
-
      <!-- Address -->
-
      <div class="txt-highlight">Address</div>
-
      <div class="layout-desktop">
-
        <Address {wallet} {profile} address={profile.address} />
-
      </div>
-
      <div class="layout-mobile">
-
        <Address compact {wallet} {profile} address={profile.address} />
-
      </div>
-
      <div class="layout-desktop" />
-
      <!-- Owner -->
-
      {#if profile.org}
-
        <div class="txt-highlight">Owner</div>
-
        <div class="layout-desktop">
-
          <Address resolve {wallet} address={profile.org.owner} />
-
        </div>
-
        <div class="layout-mobile">
-
          <Address compact resolve {wallet} address={profile.org.owner} />
-
        </div>
-
        <div class="layout-desktop" />
-
      {/if}
-
      <!-- Org Name/Profile -->
-
      <div class="txt-highlight">Profile</div>
-
      {#if profile.org}
-
        {#if utils.isAddressEqual(profile.address, profile.org.address)}
-
          <div class="overflow-text">
-
            {#if profile.name && profile.ens}
-
              <Link
-
                route={{
-
                  resource: "registrations",
-
                  params: {
-
                    view: {
-
                      resource: "view",
-
                      params: { nameOrDomain: profile.ens.name, retry: false },
-
                    },
-
                  },
-
                }}>
-
                <span class="txt-link">{profile.name}</span>
-
              </Link>
-
            {:else}
-
              <span class="txt-missing">Not set</span>
-
            {/if}
-
          </div>
-
        {/if}
-
      {:else}
-
        <!-- User Profile -->
-
        <div>
-
          {#if profile.name && profile.ens}
-
            <Link
-
              route={{
-
                resource: "registrations",
-
                params: {
-
                  view: {
-
                    resource: "view",
-
                    params: { nameOrDomain: profile.ens.name, retry: false },
-
                  },
-
                },
-
              }}>
-
              <span class="txt-link">{profile.name}</span>
-
            </Link>
-
          {:else}
-
            <span class="txt-missing">Not set</span>
-
          {/if}
-
        </div>
-
        <div class="layout-desktop">
-
          {#if isUserAuthorized(profile.address) && !profile.org}
-
            <Button
-
              variant="secondary"
-
              size="small"
-
              on:click={() => {
-
                modal.show({
-
                  component: SetNameModal,
-
                  props: {
-
                    entity: new User(profile.address),
-
                    wallet,
-
                  },
-
                });
-
              }}>
-
              Set
-
            </Button>
-
          {/if}
-
        </div>
-
      {/if}
-
      {#if vestingInfo}
-
        <div class="txt-highlight">Vesting Beneficiary</div>
-
        <div style:display="flex" style:gap="1rem">
-
          <Address address={vestingInfo.beneficiary} {wallet} resolve compact />
-
        </div>
-
        <div class="layout-desktop" />
-
        <div class="txt-highlight">Allocation</div>
-
        <div>
-
          {vestingInfo.totalVesting}
-
          <span class="txt-bold">{vestingInfo.symbol}</span>
-
        </div>
-
        <div class="layout-desktop" />
-
        <div class="txt-highlight">Withdrawn</div>
-
        <div>
-
          {vestingInfo.withdrawn}
-
          <span class="txt-bold">{vestingInfo.symbol}</span>
-
        </div>
-
        <div class="layout-desktop" />
-
        <div class="txt-highlight">Withdrawable</div>
-
        <div>
-
          {vestingInfo.withdrawableBalance}
-
          <span class="txt-bold">{vestingInfo.symbol}</span>
-
        </div>
-
        <div class="layout-desktop">
-
          {#if isUserAuthorized(vestingInfo.beneficiary) && parseFloat(vestingInfo.withdrawableBalance) > 0}
-
            <Button
-
              variant="secondary"
-
              size="small"
-
              on:click={() => {
-
                if (vestingInfo) {
-
                  modal.show({
-
                    component: WithdrawModal,
-
                    props: {
-
                      beneficiary: vestingInfo.beneficiary,
-
                      contractAddress: addressOrName,
-
                      balance: vestingInfo.withdrawableBalance,
-
                      currency: vestingInfo.symbol,
-
                      wallet,
-
                    },
-
                  });
-
                }
-
              }}>
-
              Withdraw
-
            </Button>
-
          {/if}
-
        </div>
-
        <div class="txt-highlight">Start Time</div>
-
        <div>
-
          <span>
-
            {parseVestingPeriods(vestingInfo.vestingStartTime)}
-
          </span>
-
        </div>
-
        <div class="layout-desktop" />
-
        <div class="txt-highlight">Cliff Period End</div>
-
        <div>
-
          <span>
-
            {parseVestingPeriods(
-
              vestingInfo.vestingStartTime,
-
              vestingInfo.cliffPeriod,
-
            )}
-
          </span>
-
        </div>
-
        <div class="layout-desktop" />
-
        <div class="txt-highlight">Vesting Period End</div>
-
        <div>
-
          <span>
-
            {parseVestingPeriods(
-
              vestingInfo.vestingStartTime,
-
              vestingInfo.vestingPeriod,
-
            )}
-
          </span>
-
        </div>
-
        <div class="layout-desktop" />
-
      {/if}
-
    </div>
-

-
    {#if profile.seed?.valid}
-
      <Async fetch={getProjectsAndStats(profile.seed, profile.id)} let:result>
-
        <Projects
-
          {profile}
-
          seed={profile.seed}
-
          stats={result.stats}
-
          projects={result.projects} />
-
      </Async>
-
    {/if}
-
  </main>
-
{:catch err}
-
  <div class="container">
-
    {#if err instanceof NotFoundError}
-
      <NotFound
-
        title={addressOrName}
-
        subtitle="Sorry, the requested address or domain was not found." />
-
    {:else if err instanceof MissingReverseRecord}
-
      <NotFound
-
        title={addressOrName}
-
        subtitle="Sorry, the requested name has no reverse record set." />
-
    {:else}
-
      <Error
-
        title={`Could not load "${addressOrName}".`}
-
        message={`Error: ${err.message}`} />
-
    {/if}
-
  </div>
-
{/await}
deleted src/views/profiles/SetNameModal.svelte
@@ -1,145 +0,0 @@
-
<script lang="ts" strictEvents>
-
  import type { Wallet } from "@app/lib/wallet";
-
  import type { User } from "@app/lib/profile";
-

-
  import * as modal from "@app/lib/modal";
-
  import * as router from "@app/lib/router";
-
  import * as utils from "@app/lib/utils";
-
  import { formatAddress, isAddressEqual } from "@app/lib/utils";
-

-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-
  import Modal from "@app/components/Modal.svelte";
-
  import Loading from "@app/components/Loading.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";
-

-
  import SuccessModal from "@app/views/profiles/SetNameModal/SuccessModal.svelte";
-

-
  export let entity: User;
-
  export let wallet: Wallet;
-

-
  let name = "";
-
  let state: "idle" | "checking" | "signing" | "pending" = "idle";
-

-
  const submit = async () => {
-
    if (!valid) {
-
      return;
-
    }
-
    state = "checking";
-

-
    const domain = `${name}.${wallet.registrar.domain}`;
-
    const resolved = await wallet.provider.resolveName(domain);
-

-
    if (resolved && isAddressEqual(resolved, entity.address)) {
-
      modal.disableHide();
-
      try {
-
        state = "signing";
-
        const tx = await entity.setName(domain, wallet);
-

-
        state = "pending";
-
        await tx.wait();
-
        modal.show({
-
          component: SuccessModal,
-
          props: {
-
            name,
-
            domain: wallet.registrar.domain,
-
            address: entity.address,
-
          },
-
        });
-
      } catch (error: unknown) {
-
        let message: string;
-
        if (error instanceof Error) {
-
          message = error.message;
-
        } else {
-
          message = "Unknown error. Check dev console for details.";
-
          console.error(error);
-
        }
-
        modal.show({
-
          component: ErrorModal,
-
          props: {
-
            title: "Transaction failed",
-
            error: message,
-
          },
-
        });
-
      }
-
    } else {
-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Registration mismatch",
-
          caption: `The name ${domain} does not resolve to ${utils.formatAddress(
-
            entity.address,
-
          )}. Please update the ENS record for ${domain} to the correct address and try again.`,
-
          primaryAction: {
-
            name: "Go to registration",
-
            callback: () => {
-
              modal.hide();
-
              router.push({
-
                resource: "registrations",
-
                params: {
-
                  view: {
-
                    resource: "view",
-
                    params: { nameOrDomain: domain, retry: false },
-
                  },
-
                },
-
              });
-
            },
-
          },
-
        },
-
      });
-
    }
-
  };
-

-
  $: valid = name !== "" && state === "idle";
-

-
  $: primaryAction = ["idle", "checking"].includes(state)
-
    ? { name: "Submit", callback: submit, props: { disabled: !valid } }
-
    : undefined;
-
</script>
-

-
<Modal
-
  emoji="🧣"
-
  title="Associate profile"
-
  {primaryAction}
-
  closeAction={state === "idle" || state === "checking"
-
    ? { name: "Cancel" }
-
    : false}>
-
  <div slot="subtitle">
-
    {#if state === "signing"}
-
      Please confirm the transaction in your wallet.
-
    {:else if state === "pending"}
-
      Waiting for transaction to be processed…
-
    {:else}
-
      Set a ENS name for <span class="txt-bold">
-
        {formatAddress(entity.address)}
-
      </span>
-
      to associate a profile.
-
    {/if}
-
  </div>
-

-
  <div slot="body">
-
    {#if state === "idle" || state === "checking"}
-
      <div style:margin-bottom="1.5rem">
-
        ENS profiles provide human-identifiable data to your profile, such as a
-
        <br />
-
        unique name, avatar and URL, and help make your profile more discoverable.
-
      </div>
-
      <div style:width="25rem" style:margin="auto">
-
        <TextInput
-
          autofocus
-
          disabled={state !== "idle"}
-
          on:submit={submit}
-
          loading={state === "checking"}
-
          {valid}
-
          bind:value={name}>
-
          <svelte:fragment slot="right">
-
            .{wallet.registrar.domain}
-
          </svelte:fragment>
-
        </TextInput>
-
      </div>
-
    {:else}
-
      <div style:margin-top="1.5rem">
-
        <Loading noDelay small center />
-
      </div>
-
    {/if}
-
  </div>
-
</Modal>
deleted src/views/profiles/SetNameModal/SuccessModal.svelte
@@ -1,17 +0,0 @@
-
<script lang="ts" strictEvents>
-
  import * as utils from "@app/lib/utils";
-
  import Modal from "@app/components/Modal.svelte";
-

-
  export let address: string;
-
  export let name: string;
-
  export let domain: string;
-
</script>
-

-
<Modal emoji="✅" title="Associate profile" closeAction={{ name: "Done" }}>
-
  <div slot="subtitle" style:margin-bottom="1rem">
-
    The ENS name for {utils.formatAddress(address)}
-
    <br />
-
    was set to
-
    <span class="txt-bold">{name}.{domain}</span>
-
  </div>
-
</Modal>
modified src/views/projects/Issue.svelte
@@ -1,15 +1,14 @@
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
  import type { Blob, Project } from "@app/lib/project";
-
  import { canonicalize, capitalize } from "@app/lib/utils";
-
  import { formatObjectId } from "@app/lib/cobs";
-
  import Comment from "@app/components/Comment.svelte";
  import type { Issue } from "@app/lib/issue";
+

  import Authorship from "@app/components/Authorship.svelte";
+
  import Comment from "@app/components/Comment.svelte";
+
  import { canonicalize, capitalize } from "@app/lib/utils";
+
  import { formatObjectId } from "@app/lib/cobs";

  export let issue: Issue;
  export let project: Project;
-
  export let wallet: Wallet;

  // Get an image blob based on a relative path.
  const getImage = async (imagePath: string): Promise<Blob> => {
@@ -129,7 +128,6 @@
      </div>
    </div>
    <Authorship
-
      {wallet}
      author={issue.author}
      timestamp={issue.timestamp}
      caption="opened on" />
@@ -137,16 +135,16 @@
  <main>
    <div class="comments">
      {#if !window.HEARTWOOD}
-
        <Comment comment={issue.comment} {getImage} {wallet} />
+
        <Comment comment={issue.comment} {getImage} />
      {/if}

      {#each issue.discussion as comment}
-
        <Comment {comment} {getImage} {wallet} />
+
        <Comment {comment} {getImage} />
        {#if !window.HEARTWOOD}
          {#if comment.replies}
            <div class="replies">
              {#each comment.replies as reply}
-
                <Comment comment={reply} {getImage} {wallet} />
+
                <Comment comment={reply} {getImage} />
              {/each}
            </div>
          {/if}
modified src/views/projects/Issue/IssueTeaser.svelte
@@ -1,28 +1,12 @@
<script lang="ts">
  import type { Issue } from "@app/lib/issue";
-
  import type { Wallet } from "@app/lib/wallet";

-
  import { Profile, ProfileType } from "@app/lib/profile";
  import { formatObjectId } from "@app/lib/cobs";
-
  import { onMount } from "svelte";
  import { twemoji } from "@app/lib/utils";

  import Authorship from "@app/components/Authorship.svelte";

  export let issue: Issue;
-
  export let wallet: Wallet;
-

-
  let profile: Profile | null = null;
-

-
  onMount(async () => {
-
    if (issue.author.profile?.ens?.name) {
-
      profile = await Profile.get(
-
        issue.author.profile.ens.name,
-
        ProfileType.Minimal,
-
        wallet,
-
      );
-
    }
-
  });

  const commentCount = issue.countComments();
</script>
@@ -115,8 +99,6 @@
      <span class="issue-id">{formatObjectId(issue.id)}</span>
    </div>
    <Authorship
-
      {profile}
-
      {wallet}
      caption="opened"
      author={issue.author}
      timestamp={issue.timestamp} />
modified src/views/projects/Issues.svelte
@@ -3,7 +3,6 @@
</script>

<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
  import type { Issue } from "@app/lib/issue";
  import type { ToggleButtonOption } from "@app/components/ToggleButton.svelte";

@@ -15,7 +14,6 @@
  import Placeholder from "@app/components/Placeholder.svelte";
  import ToggleButton from "@app/components/ToggleButton.svelte";

-
  export let wallet: Wallet;
  export let issues: Issue[];
  export let state: State;

@@ -84,7 +82,7 @@
              },
            });
          }}>
-
          <IssueTeaser {wallet} {issue} />
+
          <IssueTeaser {issue} />
        </div>
      {/each}
    </div>
modified src/views/projects/Patch.svelte
@@ -1,5 +1,4 @@
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
  import type { Project } from "@app/lib/project";

  import { capitalize } from "@app/lib/utils";
@@ -16,7 +15,6 @@

  export let patch: Patch;
  export let project: Project;
-
  export let wallet: Wallet;

  const onSwitch = ({ detail }: { detail: PatchTab }) => {
    activeTab = detail;
@@ -116,8 +114,6 @@
      </div>
    </div>
    <Authorship
-
      noAvatar
-
      {wallet}
      author={patch.author}
      timestamp={patch.timestamp}
      caption="opened" />
@@ -133,7 +129,7 @@
  <main>
    {#if activeTab === PatchTab.Timeline}
      <div class="flex">
-
        <PatchTimeline {patch} {revisionNumber} {wallet} {project} />
+
        <PatchTimeline {patch} {revisionNumber} {project} />
        <PatchSideBar {patch} />
      </div>
    {:else if activeTab === PatchTab.Diff && revision.changeset}
modified src/views/projects/Patch/PatchTeaser.svelte
@@ -1,28 +1,12 @@
<script lang="ts">
  import type { Patch } from "@app/lib/patch";
-
  import type { Wallet } from "@app/lib/wallet";

-
  import { Profile, ProfileType } from "@app/lib/profile";
  import { formatObjectId } from "@app/lib/cobs";
-
  import { onMount } from "svelte";
  import { twemoji } from "@app/lib/utils";

  import Authorship from "@app/components/Authorship.svelte";

  export let patch: Patch;
-
  export let wallet: Wallet;
-

-
  let profile: Profile | null = null;
-

-
  onMount(async () => {
-
    if (patch.author.profile?.ens?.name) {
-
      profile = await Profile.get(
-
        patch.author.profile.ens.name,
-
        ProfileType.Minimal,
-
        wallet,
-
      );
-
    }
-
  });

  const commentCount = patch.countComments(patch.revisions.length - 1);
</script>
@@ -115,8 +99,6 @@
      <span class="patch-id">{formatObjectId(patch.id)}</span>
    </div>
    <Authorship
-
      {profile}
-
      {wallet}
      caption="opened"
      author={patch.author}
      timestamp={patch.timestamp} />
modified src/views/projects/Patch/PatchTimeline.svelte
@@ -1,16 +1,15 @@
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-
  import { Patch, TimelineType } from "@app/lib/patch";
-
  import { formatSeedId } from "@app/lib/utils";
-
  import { canonicalize } from "@app/lib/utils";
-
  import Comment from "@app/components/Comment.svelte";
  import type { Blob, Project } from "@app/lib/project";
+

  import Authorship from "@app/components/Authorship.svelte";
+
  import Comment from "@app/components/Comment.svelte";
  import Review from "@app/views/projects/Patch/Review.svelte";
+
  import { Patch, TimelineType } from "@app/lib/patch";
+
  import { canonicalize } from "@app/lib/utils";
+
  import { formatSeedId } from "@app/lib/utils";

  export let patch: Patch;
  export let revisionNumber: number;
-
  export let wallet: Wallet;
  export let project: Project;

  $: timeline = patch.createTimeline(revisionNumber);
@@ -48,28 +47,27 @@
            profile: element.inner.peer.person,
          }}
          caption={`merged to ${formatSeedId(element.inner.peer.id)}`}
-
          timestamp={element.timestamp}
-
          {wallet} />
+
          timestamp={element.timestamp} />
      </div>
    {:else if element.type === TimelineType.Review && element.inner.author.profile?.ens?.name}
      <div class="margin-left">
-
        <Review review={element.inner} {wallet} {getImage} />
+
        <Review review={element.inner} {getImage} />
      </div>
    {:else if element.type === TimelineType.Comment}
      <div class="margin-left">
        <!-- Since the element variable only experiences changes on the inner property,
        this component has to be forced to be rerendered when element.inner changes -->
        {#key element.inner}
-
          <Comment comment={element.inner} {wallet} {getImage} />
+
          <Comment comment={element.inner} {getImage} />
        {/key}
      </div>
    {:else if element.type === TimelineType.Thread}
      <div class="margin-left">
-
        <Comment comment={element.inner} {wallet} {getImage} />
+
        <Comment comment={element.inner} {getImage} />
        {#if !window.HEARTWOOD}
          <div class="replies">
            {#each element.inner.replies as comment}
-
              <Comment caption="replied" {comment} {wallet} {getImage} />
+
              <Comment caption="replied" {comment} {getImage} />
            {/each}
          </div>
        {/if}
modified src/views/projects/Patch/Review.svelte
@@ -1,29 +1,13 @@
<script lang="ts">
-
  import { onMount } from "svelte";
-
  import type { Wallet } from "@app/lib/wallet";
  import type { Review } from "@app/lib/patch";
-
  import { formatVerdict } from "@app/lib/patch";
  import type { Blob } from "@app/lib/project";
-
  import { Profile, ProfileType } from "@app/lib/profile";
-
  import Authorship from "@app/components/Authorship.svelte";

+
  import Authorship from "@app/components/Authorship.svelte";
  import Comment from "@app/components/Comment.svelte";
+
  import { formatVerdict } from "@app/lib/patch";

  export let review: Review;
-
  export let wallet: Wallet;
  export let getImage: (path: string) => Promise<Blob>;
-

-
  let profile: Profile | null = null;
-

-
  onMount(async () => {
-
    if (review.author.profile?.ens?.name) {
-
      profile = await Profile.get(
-
        review.author.profile.ens.name,
-
        ProfileType.Minimal,
-
        wallet,
-
      );
-
    }
-
  });
</script>

<style>
@@ -34,15 +18,12 @@

{#if review.comment.body}
  <Comment
-
    {wallet}
    {getImage}
    comment={review.comment}
    caption={formatVerdict(review.verdict)} />
{:else}
  <div>
    <Authorship
-
      {wallet}
-
      {profile}
      author={review.author}
      timestamp={review.timestamp}
      caption={formatVerdict(review.verdict)} />
modified src/views/projects/Patches.svelte
@@ -3,7 +3,6 @@
</script>

<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
  import type { Patch } from "@app/lib/patch";
  import type { ToggleButtonOption } from "@app/components/ToggleButton.svelte";

@@ -16,7 +15,6 @@
  import * as router from "@app/lib/router";

  export let state: State;
-
  export let wallet: Wallet;
  export let patches: Patch[];

  let options: ToggleButtonOption<State>[];
@@ -81,7 +79,7 @@
              view: { resource: "patch", params: { patch: patch.id } },
            });
          }}>
-
          <PatchTeaser {wallet} {patch} />
+
          <PatchTeaser {patch} />
        </div>
      {/each}
    </div>
modified src/views/projects/ProjectMeta.svelte
@@ -1,15 +1,13 @@
<script lang="ts">
  import type { PeerId, Project } from "@app/lib/project";

-
  import Avatar from "@app/components/Avatar.svelte";
  import Clipboard from "@app/components/Clipboard.svelte";
  import DOMPurify from "dompurify";
-
  import Link from "@app/components/Link.svelte";
  import ProjectLink from "@app/components/ProjectLink.svelte";
  import { formatSeedId } from "@app/lib/utils";

  export let project: Project;
-
  export let peer: PeerId | null = null;
+
  export let peer: PeerId | undefined = undefined;

  const linkifyDescription = (text: string) => {
    return text.replaceAll(/(https?:\/\/[^\s]+)/g, `<a href="$1">$1</a>`);
@@ -35,11 +33,6 @@
    display: flex;
    align-items: center;
  }
-
  .org-avatar {
-
    display: inline-block;
-
    width: 2rem;
-
    height: 2rem;
-
  }
  .project-name:hover {
    color: inherit;
  }
@@ -80,21 +73,6 @@

<header class="content">
  <div class="title txt-bold txt-title">
-
    {#if project.profile}
-
      <Link
-
        route={{
-
          resource: "profile",
-
          params: { addressOrName: project.profile.addressOrName },
-
        }}
-
        title={project.profile.addressOrName}>
-
        <span class="org-avatar">
-
          <Avatar
-
            source={project.profile.avatar || project.profile.address}
-
            title={project.profile.address} />
-
        </span>
-
      </Link>
-
      <span class="divider">/</span>
-
    {/if}
    <span class="truncate">
      <ProjectLink
        projectParams={{
modified src/views/projects/View.svelte
@@ -1,5 +1,4 @@
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
  import type { ProjectRoute } from "@app/lib/router/definitions";
  import type { State as IssueState } from "./Issues.svelte";
  import type { State as PatchState } from "./Patches.svelte";
@@ -24,25 +23,18 @@
  import Message from "@app/components/Message.svelte";
  import Placeholder from "@app/components/Placeholder.svelte";

-
  export let wallet: Wallet;
  export let activeRoute: ProjectRoute;

  $: id = activeRoute.params.id;
-
  $: peer = activeRoute.params.peer ?? null;
-
  $: seed = activeRoute.params.seed ?? null;
-
  $: profile = activeRoute.params.profile ?? null;
+
  $: peer = activeRoute.params.peer;
+
  $: seed = activeRoute.params.seed;

  $: searchParams = new URLSearchParams(activeRoute.params.search || "");
  $: issueFilter = (searchParams.get("state") as IssueState) || "open";
  $: patchFilter = (searchParams.get("state") as PatchState) || "proposed";

-
  const getProject = async (
-
    id: string,
-
    peer: string | null,
-
    profile: string | null,
-
    seed: string | null,
-
  ) => {
-
    const project = await proj.Project.get(id, peer, profile, seed, wallet);
+
  const getProject = async (id: string, seed: string, peer?: string) => {
+
    const project = await proj.Project.get(id, seed, peer);
    if (activeRoute.params.route) {
      const { revision, path } = proj.parseRoute(
        activeRoute.params.route,
@@ -86,13 +78,6 @@
    padding: 0 2rem 0 8rem;
  }

-
  .container {
-
    display: flex;
-
    justify-content: center;
-
    align-items: center;
-
    height: 100%;
-
  }
-

  @media (max-width: 960px) {
    main > header {
      padding-left: 2rem;
@@ -107,7 +92,7 @@
  }
</style>

-
{#await getProject(id, peer, profile, seed)}
+
{#await getProject(id, seed, peer)}
  <main>
    <header>
      <Loading center />
@@ -147,7 +132,7 @@
        {#await issue.Issue.getIssues(project.id, project.seed.addr)}
          <Loading center />
        {:then issues}
-
          <Issues state={issueFilter} {wallet} {issues} />
+
          <Issues state={issueFilter} {issues} />
        {:catch e}
          <div class="message">
            <Message error>{e.message}</Message>
@@ -157,7 +142,7 @@
        {#await issue.Issue.getIssue(project.id, activeRoute.params.view.params.issue, project.seed.addr)}
          <Loading center />
        {:then issue}
-
          <Issue {project} {wallet} {issue} />
+
          <Issue {project} {issue} />
        {:catch e}
          <div class="message">
            <Message error>{e.message}</Message>
@@ -167,7 +152,7 @@
        {#await patch.Patch.getPatches(project.id, project.seed.addr)}
          <Loading center />
        {:then patches}
-
          <Patches {wallet} state={patchFilter} {patches} />
+
          <Patches state={patchFilter} {patches} />
        {:catch e}
          <div class="message">
            <Message error>{e.message}</Message>
@@ -177,7 +162,7 @@
        {#await patch.Patch.getPatch(project.id, activeRoute.params.view.params.patch, project.seed.addr)}
          <Loading center />
        {:then patch}
-
          <Patch {project} {wallet} {patch} />
+
          <Patch {project} {patch} />
        {:catch e}
          <div class="message">
            <Message error>{e.message}</Message>
@@ -212,7 +197,7 @@
    {/await}
  </main>
{:catch}
-
  <div class="container">
+
  <div class="layout-centered">
    <NotFound subtitle={id} title="This project was not found" />
  </div>
{/await}
deleted src/views/registrations/CheckNameModal.svelte
@@ -1,126 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { onMount } from "svelte";
-

-
  import * as modal from "@app/lib/modal";
-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-
  import Loading from "@app/components/Loading.svelte";
-
  import Modal from "@app/components/Modal.svelte";
-
  import RegisterNameModal from "@app/views/registrations/RegisterNameModal.svelte";
-
  import { formatAddress } from "@app/lib/utils";
-
  import { registrar } from "@app/lib/registrar";
-
  import { session } from "@app/lib/session";
-

-
  export let wallet: Wallet;
-
  export let name: string;
-
  export let owner: string | null;
-

-
  // We only support lower-case names.
-
  name = name.toLowerCase();
-

-
  let state: "checkingAvailability" | "nameAvailable" | "nameUnavailable" =
-
    "checkingAvailability";
-

-
  function register() {
-
    if ($session) {
-
      modal.show({
-
        component: RegisterNameModal,
-
        props: {
-
          wallet,
-
          session: $session,
-
          name,
-
          owner: registrationOwner,
-
        },
-
      });
-
    } else {
-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Registration failed",
-
          caption: "You must connect your wallet to register",
-
        },
-
      });
-
    }
-
  }
-

-
  onMount(async () => {
-
    try {
-
      const isAvailable = await registrar(wallet).available(name);
-

-
      if (isAvailable) {
-
        state = "nameAvailable";
-
      } else {
-
        state = "nameUnavailable";
-
      }
-
    } catch (err: any) {
-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Checking name availability failed",
-
          error: err.message,
-
        },
-
      });
-
    }
-
  });
-

-
  $: registrationOwner = owner || ($session && $session.address);
-
  $: primaryAction =
-
    state === "nameAvailable"
-
      ? $session
-
        ? {
-
            name: "Begin registration",
-
            callback: register,
-
          }
-
        : {
-
            name: "Connect to register",
-
            callback: () => {
-
              // FIXME: this is a workaround until we refactor the
-
              // wallet/session and can simplify the Connect button logic.
-
              Array.from(document.querySelectorAll("button"))
-
                .find(e => {
-
                  return e.textContent === "Connect";
-
                })
-
                ?.click();
-
            },
-
          }
-
      : undefined;
-
</script>
-

-
<style>
-
  .highlight {
-
    color: var(--color-secondary);
-
  }
-
</style>
-

-
<Modal emoji="🌐" title="Register a name" {primaryAction}>
-
  <span slot="subtitle">
-
    {name}.{wallet.registrar.domain}
-
  </span>
-

-
  <span slot="body">
-
    {#if state === "nameAvailable"}
-
      {#if registrationOwner}
-
        The name <span class="highlight">
-
          {name}
-
        </span>
-
        is available for registration
-
        <br />
-
        under the account
-
        <span class="txt-bold">{formatAddress(registrationOwner)}.</span>
-
      {:else}
-
        The name <span class="highlight">
-
          {name}
-
        </span>
-
        is available.
-
      {/if}
-
    {:else if state === "nameUnavailable"}
-
      The name <span class="highlight">{name}</span>
-
      is
-
      <span class="txt-bold">not available</span>
-
      for registration.
-
    {:else if state === "checkingAvailability"}
-
      <Loading noDelay small center />
-
    {/if}
-
  </span>
-
</Modal>
deleted src/views/registrations/RegisterNameModal.svelte
@@ -1,142 +0,0 @@
-
<script lang="ts">
-
  import type { Session } from "@app/lib/session";
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { onMount } from "svelte";
-

-
  import * as modal from "@app/lib/modal";
-
  import * as utils from "@app/lib/utils";
-
  import * as router from "@app/lib/router";
-
  import BlockTimer from "@app/views/registrations/RegisterNameModal/BlockTimer.svelte";
-
  import CheckNameModal from "@app/views/registrations/CheckNameModal.svelte";
-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-
  import Loading from "@app/components/Loading.svelte";
-
  import Modal from "@app/components/Modal.svelte";
-
  import { registerName, State, state } from "@app/lib/registrar";
-

-
  export let wallet: Wallet;
-
  export let name: string;
-
  export let owner: string | null;
-
  export let session: Session;
-

-
  const registrationOwner = owner || session.address;
-

-
  onMount(async () => {
-
    modal.disableHide();
-
    try {
-
      await registerName(name, registrationOwner, wallet);
-
    } catch (error: any) {
-
      console.error("Error", error);
-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Could not register name",
-
          error: error.message,
-
          closeAction: {
-
            callback: () => {
-
              modal.show({
-
                component: CheckNameModal,
-
                props: {
-
                  wallet,
-
                  name,
-
                  owner: null,
-
                },
-
              });
-
            },
-
          },
-
        },
-
      });
-
    }
-
  });
-

-
  let latestBlock: number;
-
  wallet.provider.on("block", (block: number) => {
-
    latestBlock = block;
-
  });
-

-
  $: closeAction =
-
    $state.connection === State.Registered
-
      ? {
-
          name: "View",
-
          callback: () => {
-
            modal.enableHide();
-
            modal.hide();
-
            router.push({
-
              resource: "registrations",
-
              params: {
-
                view: {
-
                  resource: "view",
-
                  params: {
-
                    nameOrDomain: `${name}.${wallet.registrar.domain}`,
-
                    retry: true,
-
                  },
-
                },
-
              },
-
            });
-
          },
-
          props: { variant: "foreground" as const },
-
        }
-
      : (false as const);
-
</script>
-

-
<style>
-
  .loader {
-
    display: flex;
-
    flex-direction: column;
-
    align-items: center;
-
    margin-top: 1.5rem;
-
    padding: 0 2rem;
-
  }
-
</style>
-

-
<Modal
-
  emoji={$state.connection === State.Registered ? "🎉" : "🌐"}
-
  title={`${name}.${wallet.registrar.domain}`}
-
  {closeAction}>
-
  <span slot="subtitle">
-
    {#if $state.connection === State.Connecting}
-
      Connecting…
-
    {:else if $state.connection === State.SigningPermit}
-
      Approving registration fee. <br />
-
      Please confirm in your wallet.
-
    {:else if $state.connection === State.SigningCommit}
-
      Committing to <span class="txt-bold">{name}.</span>
-
      <br />
-
      Please confirm transaction in your wallet.
-
    {:else if $state.connection === State.Committing}
-
      Waiting for <span class="txt-bold">commit</span>
-
      transaction to be processed…
-
    {:else if $state.connection === State.WaitingToRegister && $state.commitmentBlock}
-
      Waiting for commitment to mature.
-
      <br />
-
      This may take a moment.
-
    {:else if $state.connection === State.SigningRegister}
-
      Proceeding with registration.
-
      <br />
-
      Please confirm transaction in your wallet.
-
    {:else if $state.connection === State.Registering}
-
      Waiting for the <span class="txt-bold">register</span>
-
      transaction to be processed…
-
    {/if}
-
  </span>
-

-
  <span slot="body">
-
    {#if $state.connection === State.Registered}
-
      This name has been successfully registered to
-
      <span class="txt-highlight">
-
        {utils.formatAddress(registrationOwner)}.
-
      </span>
-
    {:else if $state.connection === State.WaitingToRegister && $state.commitmentBlock}
-
      <div class="loader">
-
        <BlockTimer
-
          {latestBlock}
-
          startBlock={$state.commitmentBlock}
-
          duration={$state.minAge} />
-
      </div>
-
    {:else}
-
      <div style:margin-top="1.5rem">
-
        <Loading noDelay small center />
-
      </div>
-
    {/if}
-
  </span>
-
</Modal>
deleted src/views/registrations/RegisterNameModal/BlockTimer.svelte
@@ -1,33 +0,0 @@
-
<script lang="ts">
-
  export let startBlock: number;
-
  export let duration: number;
-
  export let latestBlock: number;
-

-
  let progress: number = 0;
-

-
  $: if (latestBlock < startBlock + duration) {
-
    progress = (latestBlock - startBlock) * Math.floor(100 / duration);
-
  } else {
-
    progress = 100;
-
  }
-
</script>
-

-
<style>
-
  .container {
-
    text-align: center;
-
    height: 0.5rem;
-
    width: 100%;
-
    border-radius: var(--border-radius-small);
-
    background-color: var(--color-secondary-2);
-
  }
-
  .progress-bar {
-
    height: 0.5rem;
-
    width: 0px;
-
    border-radius: var(--border-radius-small);
-
    background-color: var(--color-secondary);
-
  }
-
</style>
-

-
<div class="container">
-
  <div class="progress-bar" style:width={`${progress}%`} />
-
</div>
deleted src/views/registrations/RegistrationForm.svelte
@@ -1,124 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import * as modal from "@app/lib/modal";
-

-
  import CheckNameModal from "@app/views/registrations/CheckNameModal.svelte";
-
  import Button from "@app/components/Button.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";
-

-
  export let wallet: Wallet;
-

-
  let input = "";
-
  let valid: boolean = false;
-
  let validationMessage: string | undefined = undefined;
-

-
  function register() {
-
    if (!valid) {
-
      return;
-
    }
-
    modal.show({
-
      component: CheckNameModal,
-
      props: {
-
        wallet,
-
        name: ensName,
-
        owner: null,
-
      },
-
    });
-
  }
-

-
  function validate(input: string) {
-
    if (input === "") {
-
      return { valid: false };
-
    }
-

-
    if (input && input.includes(".")) {
-
      return {
-
        valid: false,
-
        validationMessage: "Please do not use dots as separators.",
-
      };
-
    }
-
    if (input && input.length < 2) {
-
      return {
-
        valid: false,
-
        validationMessage: "Please enter a minimum of 2 characters.",
-
      };
-
    }
-
    if (input && input.length > 128) {
-
      return {
-
        valid: false,
-
        validationMessage: "Please enter a maximum of 128 characters.",
-
      };
-
    }
-

-
    return { valid: true };
-
  }
-

-
  $: ensName = input.trim();
-
  $: ({ valid, validationMessage } = validate(ensName));
-
</script>
-

-
<style>
-
  main {
-
    display: flex;
-
    flex-direction: column;
-
    gap: 1.5rem;
-
    height: 100%;
-
    justify-content: center;
-
    padding-bottom: 24vh;
-
    padding-top: 5rem;
-
    width: 32rem;
-
  }
-
  .title {
-
    color: var(--color-secondary);
-
    font-size: var(--font-size-medium);
-
  }
-
  .subtitle {
-
    color: var(--color-secondary);
-
  }
-
  .form {
-
    display: flex;
-
    gap: 1rem;
-
  }
-
</style>
-

-
<svelte:head>
-
  <title>Radicle &ndash; Register</title>
-
</svelte:head>
-

-
<main>
-
  <div class="title">
-
    Register a <span class="txt-bold">{wallet.registrar.domain}</span>
-
    name
-
  </div>
-

-
  <div class="subtitle">
-
    Register a unique name with our ENS registrar, under <br />
-
    the
-
    <span class="txt-bold">{wallet.registrar.domain}</span>
-
    domain (e.g. cloudhead.{wallet.registrar.domain}).
-
    <br />
-
    Radicle names never expire and free to register.
-
  </div>
-

-
  <div class="form">
-
    <TextInput
-
      bind:value={input}
-
      autofocus
-
      on:submit={register}
-
      {valid}
-
      {validationMessage}>
-
      <svelte:fragment slot="right">
-
        .{wallet.registrar.domain}
-
      </svelte:fragment>
-
    </TextInput>
-

-
    <Button
-
      disabled={!valid}
-
      variant="primary"
-
      size="regular"
-
      on:click={register}>
-
      Check
-
    </Button>
-
  </div>
-
</main>
deleted src/views/registrations/Routes.svelte
@@ -1,23 +0,0 @@
-
<script lang="ts">
-
  import type { RegistrationRoute } from "@app/lib/router/definitions";
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { unreachable } from "@app/lib/utils";
-

-
  import RegistrationForm from "@app/views/registrations/RegistrationForm.svelte";
-
  import View from "@app/views/registrations/View.svelte";
-

-
  export let wallet: Wallet;
-
  export let activeRoute: RegistrationRoute;
-
</script>
-

-
{#if activeRoute.params.view.resource === "form"}
-
  <RegistrationForm {wallet} />
-
{:else if activeRoute.params.view.resource === "view"}
-
  <View
-
    {wallet}
-
    retry={activeRoute.params.view.params.retry}
-
    domain={activeRoute.params.view.params.nameOrDomain} />
-
{:else}
-
  {unreachable(activeRoute.params.view)}
-
{/if}
deleted src/views/registrations/Update.svelte
@@ -1,75 +0,0 @@
-
<script lang="ts">
-
  import type { EnsRecord } from "@app/lib/resolver";
-
  import type { Registration } from "@app/lib/registrar";
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import { onMount } from "svelte";
-

-
  import * as modal from "@app/lib/modal";
-
  import { setRecords } from "@app/lib/resolver";
-

-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-
  import Loading from "@app/components/Loading.svelte";
-
  import Modal from "@app/components/Modal.svelte";
-

-
  export let domain: string;
-
  export let wallet: Wallet;
-
  export let records: EnsRecord[];
-
  export let registration: Registration;
-

-
  let state: "initial" | "signing" | "pending" | "success" = "initial";
-

-
  onMount(async () => {
-
    modal.disableHide();
-
    try {
-
      state = "signing";
-
      const tx = await setRecords(
-
        domain,
-
        records,
-
        registration.resolver,
-
        wallet,
-
      );
-
      state = "pending";
-
      await tx.wait();
-
      state = "success";
-
    } catch (error: any) {
-
      modal.show({
-
        component: ErrorModal,
-
        props: {
-
          title: "Updating registration failed",
-
          error: error.message,
-
        },
-
      });
-
    }
-
  });
-
</script>
-

-
<Modal
-
  emoji="🧾"
-
  title="Update registration"
-
  closeAction={state === "success"
-
    ? {
-
        name: "Done",
-
        callback: () => {
-
          location.reload();
-
        },
-
      }
-
    : false}>
-
  <span slot="subtitle">
-
    {#if state === "signing"}
-
      <p>Please confirm the transaction in your wallet</p>
-
    {:else if state === "pending"}
-
      <p>Waiting for transaction to be processed…</p>
-
    {:else if state === "success"}
-
      <p>Your registration was successfully updated</p>
-
    {/if}
-
  </span>
-

-
  <div slot="body">
-
    {#if ["signing", "pending"].includes(state)}
-
      <div style="margin-top: 1.5rem;">
-
        <Loading noDelay small center />
-
      </div>
-
    {/if}
-
  </div>
-
</Modal>
deleted src/views/registrations/View.svelte
@@ -1,315 +0,0 @@
-
<script lang="ts">
-
  import type { EnsRecord } from "@app/lib/resolver";
-
  import type { Field, RegistrationRecord } from "@app/components/Form.svelte";
-
  import type { Registration } from "@app/lib/registrar";
-
  import type { Wallet } from "@app/lib/wallet";
-
  import type { ethers } from "ethers";
-

-
  import { onMount } from "svelte";
-

-
  import * as modal from "@app/lib/modal";
-
  import * as router from "@app/lib/router";
-
  import { assert } from "@app/lib/error";
-
  import { defaultSeedPort } from "@app/lib/seed";
-
  import { getRegistration, getOwner } from "@app/lib/registrar";
-
  import { isAddressEqual, isReverseRecordSet, twemoji } from "@app/lib/utils";
-
  import { session } from "@app/lib/session";
-

-
  import Button from "@app/components/Button.svelte";
-
  import Form from "@app/components/Form.svelte";
-
  import Loading from "@app/components/Loading.svelte";
-
  import ErrorModal from "@app/components/ErrorModal.svelte";
-

-
  import Update from "@app/views/registrations/Update.svelte";
-
  import CheckNameModal from "@app/views/registrations/CheckNameModal.svelte";
-

-
  export let domain: string;
-
  export let wallet: Wallet;
-
  export let retry: boolean;
-

-
  domain = domain.toLowerCase();
-

-
  if (!domain.includes(".")) {
-
    domain = `${domain}.${wallet.registrar.domain}`;
-
  }
-

-
  let state:
-
    | { type: "loading" }
-
    | { type: "notFound" }
-
    | { type: "found"; registration: Registration; owner: string } = {
-
    type: "loading",
-
  };
-

-
  let editable = false;
-
  let fields: Field[] = [];
-
  let updateRecords: EnsRecord[] | null = null;
-
  let retries = 3;
-
  let resolver: ethers.providers.EnsResolver | undefined = undefined;
-

-
  async function parseRecords(
-
    r: Registration | null,
-
  ): Promise<Registration | null> {
-
    if (r) {
-
      let reverseRecord = false;
-
      if (r.profile.address) {
-
        reverseRecord = await isReverseRecordSet(
-
          r.profile.address,
-
          domain,
-
          wallet,
-
        );
-
      }
-
      const owner = await getOwner(domain, wallet);
-
      resolver = r.resolver;
-

-
      fields = [
-
        {
-
          name: "owner",
-
          validate: "address",
-
          placeholder: "",
-
          description: "The owner and controller of this name.",
-
          value: owner,
-
          resolve: true,
-
          editable: false,
-
        },
-
        {
-
          name: "address",
-
          validate: "address",
-
          placeholder: "Ethereum address, eg. 0x4a9cf21…bc91",
-
          description:
-
            "The address this name resolves to. " +
-
            (reverseRecord
-
              ? `The reverse record for this address is set to **${domain}**`
-
              : "The reverse record for this address is **not set**. " +
-
                "For this name to be correctly associated with the address, " +
-
                "a reverse record should be set."),
-
          value: r.profile.address ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "url",
-
          label: "URL",
-
          validate: "URL",
-
          placeholder: "https://acme.org",
-
          description: "A homepage or other URL associated with this name.",
-
          value: r.profile.url ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "avatar",
-
          validate: "URL",
-
          placeholder: "https://acme.org/avatar.png",
-
          description: "An avatar or square image associated with this name.",
-
          value: r.profile.avatar ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "twitter",
-
          validate: "handle",
-
          placeholder: "Twitter username, eg. 'acme'",
-
          description: "The Twitter handle associated with this name.",
-
          value: r.profile.twitter ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "github",
-
          validate: "handle",
-
          label: "GitHub",
-
          placeholder: "GitHub username, eg. 'acme'",
-
          description: "The GitHub username associated with this name.",
-
          value: r.profile.github ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "id",
-
          label: "Radicle",
-
          validate: "identity",
-
          placeholder: window.HEARTWOOD
-
            ? "Radicle ID, eg. rad:zKtT7DmF9H34KkvcKj…"
-
            : "Radicle ID, eg. rad:git:hnrkqdpm9ub19oc8d…",
-
          description: "The local radicle identity associated with this name.",
-
          value: r.profile.id ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "seed.host",
-
          label: "Seed Host",
-
          validate: "domain",
-
          placeholder: "seed.acme.org",
-
          url: r.profile.seed && `/seeds/${r.profile.seed.host}`,
-
          description:
-
            "The seed host address. " +
-
            "Only domain names with TLS are supported. " +
-
            `HTTP(S) API requests use port ${defaultSeedPort}.`,
-
          value: r.profile.seed?.host ?? "",
-
          editable: true,
-
        },
-
        {
-
          name: "seed.id",
-
          label: "Seed ID",
-
          validate: "id",
-
          placeholder: "hynkyndc6w3p8urucakobzncqny7xxtw88…",
-
          description:
-
            "The Device ID of a Radicle Link node that hosts entities associated with this name.",
-
          value: r.profile.seed?.id ?? "",
-
          editable: true,
-
        },
-
      ];
-
      state = { type: "found", registration: r, owner };
-
    } else {
-
      state = { type: "notFound" };
-
    }
-
    if (retry) retries -= 1;
-
    return r;
-
  }
-

-
  function showErrorModal(message: string) {
-
    modal.show({
-
      component: ErrorModal,
-
      props: {
-
        title: "Registration could not be loaded",
-
        error: message,
-
        closeAction: {
-
          callback: () => {
-
            router.push({
-
              resource: "registrations",
-
              params: { view: { resource: "form" } },
-
            });
-
            modal.hide();
-
          },
-
        },
-
      },
-
    });
-
  }
-

-
  onMount(() => {
-
    getRegistration(domain, wallet, resolver)
-
      .then(parseRecords)
-
      .catch(error => {
-
        showErrorModal(error.message);
-
      });
-
  });
-

-
  const onSave = async (event: { detail: RegistrationRecord[] }) => {
-
    assert(state.type === "found", "registration must be found");
-

-
    updateRecords = event.detail.map(f => {
-
      return { name: f.name, value: f.value };
-
    });
-

-
    modal.show({
-
      component: Update,
-
      props: {
-
        wallet,
-
        domain,
-
        registration: state.registration,
-
        records: updateRecords,
-
      },
-
    });
-
  };
-

-
  $: if (retry && state.type === "notFound" && retries > 0) {
-
    getRegistration(domain, wallet, resolver)
-
      .then(parseRecords)
-
      .catch(error => {
-
        showErrorModal(error.message);
-
      });
-
  }
-

-
  $: isOwner = (owner: string): boolean => {
-
    return $session ? isAddressEqual(owner, $session.address) : false;
-
  };
-
</script>
-

-
<style>
-
  main {
-
    padding: 5rem 0;
-
  }
-
  main > header {
-
    display: flex;
-
    align-items: center;
-
    justify-content: left;
-
    margin-bottom: 2rem;
-
  }
-
  main > header > * {
-
    margin: 0 1rem 0 0;
-
  }
-
  .emoji {
-
    font-size: var(--font-size-xx-large);
-
  }
-
  @media (max-width: 720px) {
-
    main {
-
      width: 100%;
-
      padding-left: 1rem;
-
      padding-right: 1rem;
-
    }
-
  }
-

-
  .placeholder {
-
    text-align: center;
-
  }
-
  .container {
-
    height: 100%;
-
    display: flex;
-
    align-items: center;
-
    justify-content: center;
-
  }
-
</style>
-

-
<svelte:head>
-
  <title>{domain}</title>
-
</svelte:head>
-

-
{#if state.type === "loading"}
-
  <Loading />
-
{:else if state.type === "notFound"}
-
  <div class="container">
-
    <div class="placeholder">
-
      <div class="emoji" use:twemoji>🍄</div>
-
      <div class="txt-highlight txt-medium txt-bold">{domain}</div>
-
      <p style:margin-bottom="2rem">
-
        The name <span class="txt-bold">{domain}</span>
-
        is not registered.
-
      </p>
-
      <Button
-
        on:click={() => {
-
          modal.show({
-
            component: CheckNameModal,
-
            props: {
-
              name: domain.replace(`.${wallet.registrar.domain}`, ""),
-
              wallet,
-
              owner: null,
-
            },
-
          });
-
        }}
-
        variant="primary">
-
        Register
-
      </Button>
-
    </div>
-
  </div>
-
{:else if state.type === "found"}
-
  <main>
-
    <header>
-
      <div class="txt-title txt-bold">{domain}</div>
-
      <div style="width: 4rem;">
-
        {#if !editable}
-
          <Button
-
            size="small"
-
            variant="primary"
-
            disabled={!isOwner(state.owner)}
-
            title={!isOwner(state.owner)
-
              ? "Only owner can edit this profile"
-
              : ""}
-
            on:click={() => (editable = !editable)}>
-
            Edit
-
          </Button>
-
        {/if}
-
      </div>
-
    </header>
-
    <Form
-
      {wallet}
-
      {editable}
-
      {fields}
-
      on:save={onSave}
-
      on:cancel={() => (editable = false)} />
-
  </main>
-
{/if}
modified src/views/seeds/View.svelte
@@ -8,7 +8,6 @@
  import NotFound from "@app/components/NotFound.svelte";
  import Clipboard from "@app/components/Clipboard.svelte";
  import Projects from "@app/views/seeds/View/Projects.svelte";
-
  import Async from "@app/components/Async.svelte";
  import { Project } from "@app/lib/project";
  import type { Host } from "@app/lib/api";

@@ -117,14 +116,23 @@
      <div class="layout-desktop" />
    </div>
    <!-- Seed Projects -->
-
    <Async fetch={getProjectsAndStats(seed)} let:result>
+
    {#await getProjectsAndStats(seed)}
+
      <Loading center />
+
    {:then result}
      <Projects {seed} projects={result.projects} stats={result.stats} />
-
    </Async>
+
    {:catch err}
+
      <div class="error txt-tiny">
+
        <div>
+
          API request to <span class="txt-monospace">{err.url}</span>
+
          failed.
+
        </div>
+
      </div>
+
    {/await}
  </main>
{:catch}
-
  <main class="layout-centered">
+
  <div class="layout-centered">
    <NotFound
      title={host}
      subtitle="Not able to query information from this seed." />
-
  </main>
+
  </div>
{/await}
modified src/views/seeds/View/Projects.svelte
@@ -1,5 +1,4 @@
<script lang="ts">
-
  import type { Profile } from "@app/lib/profile";
  import type { ProjectInfo } from "@app/lib/project";
  import type { Seed, Stats } from "@app/lib/seed";

@@ -9,7 +8,6 @@
  import Widget from "@app/views/projects/Widget.svelte";

  export let seed: Seed;
-
  export let profile: Profile | null = null;
  export let projects: proj.ProjectInfo[];
  export let stats: Stats;

@@ -44,7 +42,6 @@
        seed: seed.addr.port
          ? `${seed.addr.host}:${seed.addr.port}`
          : seed.addr.host,
-
        profile: profile?.name ?? profile?.address,
        revision: project.head ?? undefined,
        hash: undefined,
        search: undefined,
deleted src/views/vesting/Form.svelte
@@ -1,90 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import * as utils from "@app/lib/utils";
-
  import * as router from "@app/lib/router";
-
  import Button from "@app/components/Button.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";
-
  import { state, getInfo } from "@app/lib/vesting";
-

-
  export let wallet: Wallet;
-

-
  let contractAddress = "";
-

-
  $: valid = utils.isAddress(contractAddress);
-
  $: validationMessage =
-
    contractAddress !== "" && !valid
-
      ? "Please enter a valid Ethereum address."
-
      : "";
-

-
  const loadContract = async () => {
-
    state.set({ type: "loading" });
-
    try {
-
      const info = await getInfo(contractAddress, wallet);
-
      if (info) {
-
        router.push({
-
          resource: "profile",
-
          params: { addressOrName: contractAddress },
-
        });
-
      } else {
-
        validationMessage = "No vesting account found under this address.";
-
      }
-
    } catch (error) {
-
      validationMessage =
-
        "Couldn't load contract, check dev console for details.";
-
      console.warn(error);
-
    }
-
    state.set({ type: "idle" });
-
  };
-
</script>
-

-
<style>
-
  main {
-
    display: flex;
-
    flex-direction: column;
-
    gap: 1.5rem;
-
    height: 100%;
-
    justify-content: center;
-
    padding-bottom: 24vh;
-
    padding-top: 5rem;
-
    width: 38rem;
-
  }
-
  .title {
-
    color: var(--color-secondary);
-
    font-size: var(--font-size-medium);
-
  }
-
  .form {
-
    display: flex;
-
    gap: 1rem;
-
  }
-
</style>
-

-
<svelte:head>
-
  <title>Radicle &ndash; Vesting</title>
-
</svelte:head>
-

-
<main>
-
  <div class="title">
-
    Your Radicle <span class="txt-bold">vesting contract</span>
-
  </div>
-

-
  <div class="form">
-
    <TextInput
-
      autofocus
-
      placeholder="Enter vesting contract address"
-
      {valid}
-
      {validationMessage}
-
      loading={$state.type === "loading"}
-
      disabled={$state.type === "loading"}
-
      on:submit={loadContract}
-
      bind:value={contractAddress} />
-

-
    <Button
-
      on:click={loadContract}
-
      variant="primary"
-
      waiting={$state.type === "loading"}
-
      disabled={!valid || $state.type === "loading"}>
-
      Load
-
    </Button>
-
  </div>
-
</main>
deleted src/views/vesting/Routes.svelte
@@ -1,20 +0,0 @@
-
<script lang="ts">
-
  import type { Wallet } from "@app/lib/wallet";
-
  import type { VestingRoute } from "@app/lib/router/definitions";
-

-
  import { unreachable } from "@app/lib/utils";
-

-
  import Form from "@app/views/vesting/Form.svelte";
-
  import Profile from "@app/views/profiles/Profile.svelte";
-

-
  export let activeRoute: VestingRoute;
-
  export let wallet: Wallet;
-
</script>
-

-
{#if activeRoute.params.view.resource === "form"}
-
  <Form {wallet} />
-
{:else if activeRoute.params.view.resource === "view"}
-
  <Profile {wallet} addressOrName={activeRoute.params.view.params.contract} />
-
{:else}
-
  {unreachable(activeRoute.params.view)}
-
{/if}
deleted src/views/vesting/WithdrawModal.svelte
@@ -1,55 +0,0 @@
-
<script lang="ts" strictEvents>
-
  import type { Wallet } from "@app/lib/wallet";
-

-
  import * as modal from "@app/lib/modal";
-
  import * as utils from "@app/lib/utils";
-

-
  import Loading from "@app/components/Loading.svelte";
-
  import Modal from "@app/components/Modal.svelte";
-
  import { onMount } from "svelte";
-
  import { state } from "@app/lib/vesting";
-
  import { withdrawVested } from "@app/lib/vesting";
-

-
  export let contractAddress: string;
-
  export let beneficiary: string;
-
  export let wallet: Wallet;
-
  export let balance: string;
-
  export let currency: string;
-

-
  onMount(async () => {
-
    modal.disableHide();
-
    await withdrawVested(contractAddress, wallet);
-
    modal.enableHide();
-
  });
-
</script>
-

-
<Modal
-
  emoji="💰"
-
  title="Withdraw funds"
-
  closeAction={$state.type === "withdrawn" ? { name: "Done" } : false}>
-
  <span slot="subtitle">
-
    {#if $state.type === "withdrawingSign"}
-
      Send {balance}
-
      {currency} to
-
      {utils.formatAddress(beneficiary)}.
-
      <br />
-
      Please confirm in your wallet.
-
    {:else if $state.type === "withdrawing"}
-
      Waiting for transaction to be processed…
-
    {/if}
-
  </span>
-

-
  <span slot="body">
-
    {#if $state.type === "withdrawn"}
-
      <span>
-
        Tokens have been withdrawn to <span class="txt-highlight">
-
          {utils.formatAddress(beneficiary)}
-
        </span>
-
      </span>
-
    {:else}
-
      <div style:margin-top="1.5rem">
-
        <Loading noDelay small center />
-
      </div>
-
    {/if}
-
  </span>
-
</Modal>
modified tests/support/fixtures.ts
@@ -23,11 +23,6 @@ export const test = base.extend<{
      page.on("console", msg => {
        // Ignore common console logs that we don't care about.
        if (
-
          msg
-
            .text()
-
            .startsWith(
-
              `Module "buffer" has been externalized for browser compatibility.`,
-
            ) ||
          msg.text().startsWith("[vite] connected.") ||
          msg.text().startsWith("[vite] connecting...") ||
          msg
@@ -52,9 +47,6 @@ export const test = base.extend<{
        // access to any variables that we have in the test.
        await page.addInitScript(() => {
          window.APP_CONFIG = {
-
            walletConnect: {
-
              bridge: "https://radicle.bridge.walletconnect.org",
-
            },
            reactions: [],
            seeds: {
              pinned: [{ host: "0.0.0.0", emoji: "🚀" }],
@@ -93,27 +85,6 @@ export const test = base.extend<{
        }
      });

-
      page.on("websocket", ws => {
-
        log(`WebSocket opened: ${ws.url()}`, playwrightLabel, outputLog);
-
        ws.on("framesent", event =>
-
          log(
-
            `WebSocket framesent: ${event.payload}`,
-
            playwrightLabel,
-
            outputLog,
-
          ),
-
        );
-
        ws.on("framereceived", event =>
-
          log(
-
            `WebSocket framereceived: ${event.payload}`,
-
            playwrightLabel,
-
            outputLog,
-
          ),
-
        );
-
        ws.on("close", () =>
-
          log(`WebSocket closed`, playwrightLabel, outputLog),
-
        );
-
      });
-

      await use();
    },
    { scope: "test", auto: true },
@@ -159,9 +130,6 @@ export const appConfigWithFixture = process.env.HEARTWOOD

export function configFixture() {
  window.APP_CONFIG = {
-
    walletConnect: {
-
      bridge: "https://radicle.bridge.walletconnect.org",
-
    },
    reactions: [],
    seeds: {
      pinned: [{ host: "0.0.0.0", emoji: "🚀" }],
@@ -180,9 +148,6 @@ export function configFixture() {

export function configFixtureHeartwood() {
  window.APP_CONFIG = {
-
    walletConnect: {
-
      bridge: "https://radicle.bridge.walletconnect.org",
-
    },
    reactions: [],
    seeds: {
      pinned: [{ host: "0.0.0.0", emoji: "🚀" }],
deleted tests/unit/cache.test.ts
@@ -1,13 +0,0 @@
-
import { cached } from "@app/lib/cache";
-
import { expect, test, vi } from "vitest";
-

-
test("it caches undefined return values", async () => {
-
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-
  const inner = vi.fn(async (_: string) => undefined);
-
  const memoized = cached(inner, key => key);
-

-
  expect(await memoized("a")).toBe(undefined);
-
  expect(await memoized("a")).toBe(undefined);
-

-
  expect(inner).toHaveBeenCalledTimes(1);
-
});
modified tests/unit/router.test.ts
@@ -9,66 +9,12 @@ describe("routeToPath", () => {
  test.each([
    { input: { resource: "home" }, output: "/", description: "Home Route" },
    {
-
      input: { resource: "vesting", params: { view: { resource: "form" } } },
-
      output: "/vesting",
-
      description: "Vesting Route",
-
    },
-
    {
-
      input: { resource: "faucet" },
-
      output: "/faucet",
-
      description: "Faucet Form Route",
-
    },
-
    {
-
      input: {
-
        resource: "profile",
-
        params: { addressOrName: "cloudhead.eth" },
-
      },
-
      output: "/cloudhead.eth",
-
      description: "Profile Route",
-
    },
-
    {
      input: { resource: "seeds", params: { host: "willow.radicle.garden" } },
      output: "/seeds/willow.radicle.garden",
      description: "Seed View Route",
    },
    {
      input: {
-
        resource: "registrations",
-
        params: {
-
          view: { resource: "form" },
-
        },
-
      },
-
      output: "/registrations",
-
      description: "registrations Index Route",
-
    },
-
    {
-
      input: {
-
        resource: "registrations",
-
        params: {
-
          view: {
-
            resource: "view",
-
            params: { nameOrDomain: "sebastinez", retry: true },
-
          },
-
        },
-
      },
-
      output: "/registrations/sebastinez?retry=true",
-
      description: "registrations View Route",
-
    },
-
    {
-
      input: {
-
        resource: "registrations",
-
        params: {
-
          view: {
-
            resource: "view",
-
            params: { nameOrDomain: "sebastinez", retry: false },
-
          },
-
        },
-
      },
-
      output: "/registrations/sebastinez?retry=false",
-
      description: "registrations View Route",
-
    },
-
    {
-
      input: {
        resource: "projects",
        params: {
          view: { resource: "tree" },
@@ -95,65 +41,11 @@ describe("pathToRoute", () => {
    },
    { input: "/", output: { resource: "home" }, description: "Home Route" },
    {
-
      input: "/vesting",
-
      output: { resource: "vesting", params: { view: { resource: "form" } } },
-
      description: "Vesting Route",
-
    },
-
    {
-
      input: "/faucet",
-
      output: { resource: "faucet" },
-
      description: "Faucet Form Route",
-
    },
-
    {
-
      input: "/cloudhead.eth",
-
      output: {
-
        resource: "profile",
-
        params: { addressOrName: "cloudhead.eth" },
-
      },
-
      description: "Profile Route",
-
    },
-
    {
      input: "/seeds/willow.radicle.garden",
      output: { resource: "seeds", params: { host: "willow.radicle.garden" } },
      description: "Seed View Route",
    },
    {
-
      input: "/registrations",
-
      output: {
-
        resource: "registrations",
-
        params: {
-
          view: { resource: "form" },
-
        },
-
      },
-
      description: "registrations Index Route",
-
    },
-
    {
-
      input: "/registrations/sebastinez",
-
      output: {
-
        resource: "registrations",
-
        params: {
-
          view: {
-
            resource: "view",
-
            params: { nameOrDomain: "sebastinez", retry: false },
-
          },
-
        },
-
      },
-
      description: "registrations View Route",
-
    },
-
    {
-
      input: "/registrations/sebastinez?retry=true",
-
      output: {
-
        resource: "registrations",
-
        params: {
-
          view: {
-
            resource: "view",
-
            params: { nameOrDomain: "sebastinez", retry: true },
-
          },
-
        },
-
      },
-
      description: "registrations View Route",
-
    },
-
    {
      input: "/seeds/willow.radicle.garden/rad:zKtT7DmF9H34KkvcKj9PHW19WzjT",
      output: {
        resource: "projects",
modified tests/unit/utils.test.ts
@@ -1,26 +1,8 @@
-
import type { Wallet } from "@app/lib/wallet";
-

-
import { BigNumber } from "ethers";
import { describe, expect, test } from "vitest";
import * as utils from "@app/lib/utils";

-
describe("Conversions", () => {
-
  test("toWei", () => {
-
    expect(utils.toWei("10")).toEqual(BigNumber.from("10000000000000000000"));
-
  });
-
});
-

describe("Format functions", () => {
  test.each([
-
    { amount: "1000", digits: 2, expected: "10.0" },
-
    { amount: "10000000000000000000", expected: "10.0" },
-
  ])("formatBalance", ({ amount, digits, expected }) => {
-
    expect(utils.formatBalance(BigNumber.from(amount), digits)).toEqual(
-
      expected,
-
    );
-
  });
-

-
  test.each([
    { hash: "#L42", expected: 42 },
    { hash: "#ETH", expected: null },
  ])("formatLocationHash $hash => $expected", ({ hash, expected }) => {
@@ -54,16 +36,6 @@ describe("Format functions", () => {
    ).toThrow();
  });

-
  test("formatAddress", () => {
-
    expect(
-
      utils.formatAddress("0xb5d85cbf7cb3ee0d56b3bb207d5fc4b82f43f511"),
-
    ).toEqual("b5d8 – F511");
-

-
    expect(() => utils.formatAddress("0x8f91813")).toThrowError(
-
      'invalid address (argument="address", value="0x8f91813", code=INVALID_ARGUMENT, version=address/5.7.0)',
-
    );
-
  });
-

  test.each([
    {
      input: "seedling",
@@ -91,21 +63,6 @@ describe("Format functions", () => {

describe("String Assertions", () => {
  test.each([
-
    {
-
      a: "0x1234567890123456789012345678901234567890",
-
      b: "0x1234567890123456789012345678901234567890",
-
      expected: true,
-
    },
-
    {
-
      a: "0x1234567890123456789012345678901234567890",
-
      b: "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
-
      expected: false,
-
    },
-
  ])("isAddressEqual", ({ a, b, expected }) => {
-
    expect(utils.isAddressEqual(a, b)).toEqual(expected);
-
  });
-

-
  test.each([
    { domain: "alt-clients.radicle.xyz", expected: true },
    { domain: "0.0.0.0", expected: true }, // Pass as true since we are not in production
    { domain: "", expected: false },
@@ -146,14 +103,6 @@ describe("String Assertions", () => {
  });

  test.each([
-
    { address: "0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", expected: true },
-
    { address: "0x5E813e48a81977c6fdd565ed5097eb600c73c4f0", expected: false }, // If address is badly checksummed => false
-
    { address: "0x5e813e48a81977c6fdd565ed5097eb600c73c4f0", expected: true },
-
  ])("isAddress $address => $expected", ({ address, expected }) => {
-
    expect(utils.isAddress(address)).toBe(expected);
-
  });
-

-
  test.each([
    { url: "https://app.radicle.xyz", expected: true },
    { url: "http://app.radicle.xyz", expected: true },
    { url: "http://app", expected: true },
@@ -165,45 +114,8 @@ describe("String Assertions", () => {
  });
});

-
describe("Others", () => {
-
  test.each([
-
    {
-
      name: "goerli",
-
      expected:
-
        "https://goerli.etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
-
    },
-
    {
-
      name: "",
-
      expected:
-
        "https://etherscan.io/address/0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0",
-
    },
-
  ])("explorerLink $name => $expected", ({ name, expected }) => {
-
    expect(
-
      utils.explorerLink("0x5E813e48a81977c6Fdd565ed5097eb600C73C4f0", {
-
        network: {
-
          name,
-
        },
-
      } as Wallet),
-
    ).toEqual(expected);
-
  });
-
});
-

describe("Parse Strings", () => {
  test.each([
-
    { label: "sebastinez.radicle.eth", expected: "sebastinez" },
-
    { label: "sebastinez", expected: "sebastinez" },
-
  ])("parseEnsLabel", ({ label, expected }) => {
-
    expect(
-
      utils.parseEnsLabel(label, {
-
        registrar: {
-
          address: "0x1234567890123456789012345678901234567890",
-
          domain: "radicle.eth",
-
        },
-
      } as Wallet),
-
    ).toEqual(expected);
-
  });
-

-
  test.each([
    { input: "https://twitter.com/cloudhead", expected: "cloudhead" },
    { input: "sebastinez", expected: "sebastinez" },
  ])("parseUsername", ({ input, expected }) => {
modified vite.config.ts
@@ -26,13 +26,7 @@ function defineConstants() {
}

export default defineConfig({
-
  optimizeDeps: {
-
    exclude: ["@pedrouid/environment", "@pedrouid/iso-crypto"],
-
  },
  test: {
-
    deps: {
-
      inline: ["@ethersproject/signing-key", "@ethersproject/basex"],
-
    },
    setupFiles: "./tests/support/setupVitest",
    environment: "happy-dom",
    include: ["tests/unit/**/*.test.ts"],
@@ -64,12 +58,6 @@ export default defineConfig({
    rollupOptions: {
      output: {
        manualChunks: {
-
          ethereum: [
-
            "ethers",
-
            "@ethersproject/abstract-provider",
-
            "@walletconnect/client",
-
          ],
-
          cache: ["lru-cache", "@stardazed/streams"],
          markdown: [
            "@radicle/gray-matter",
            "dompurify",
@@ -94,7 +82,7 @@ export default defineConfig({
            "@wooorm/starry-night/lang/source.cs.js",
            "@wooorm/starry-night/lang/source.swift.js",
          ],
-
          dom: ["svelte", "pure-svg-code", "twemoji"],
+
          dom: ["svelte", "twemoji"],
        },
      },
    },