diff options
| author | Adam Soltys <[email protected]> | 2026-02-24 13:01:02 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-02-24 13:01:02 -0800 |
| commit | db59579a2efa8416f78c855faa7d7e62048447e5 (patch) | |
| tree | 28444081de0120f21f7596198937bdd52fe43495 | |
| parent | 7eec7ac0b322b510b4e2cedef62422b6043d6405 (diff) | |
| download | coinos-ui-db59579a2efa8416f78c855faa7d7e62048447e5.tar.gz coinos-ui-db59579a2efa8416f78c855faa7d7e62048447e5.zip | |
lint
29 files changed, 342 insertions, 350 deletions
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c7c9dd41 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,15 @@ +name: Lint & Format + +on: + push: + branches: [master] + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + - run: bun run lint diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..ea5a55b6 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +bunx lint-staged @@ -38,8 +38,10 @@ "daisyui": "^5.5.17", "date-fns": "^4.1.0", "esptool-js": "^0.5.7", + "husky": "^9.1.7", "iconify-icon": "^3.0.2", "js-cookie": "^3.0.5", + "lint-staged": "^16.2.7", "nostr-tools": "^2.23.0", "oxfmt": "^0.35.0", "oxlint": "^1.35.0", @@ -490,6 +492,8 @@ "aggregate-error": ["[email protected]", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], + "ansi-escapes": ["[email protected]", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], + "ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -576,6 +580,10 @@ "clean-stack": ["[email protected]", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + "cli-cursor": ["[email protected]", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-truncate": ["[email protected]", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="], + "cliui": ["[email protected]", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "clsx": ["[email protected]", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -588,6 +596,8 @@ "color-string": ["[email protected]", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], + "colorette": ["[email protected]", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "commander": ["[email protected]", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "commondir": ["[email protected]", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], @@ -704,6 +714,8 @@ "env-paths": ["[email protected]", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "error-ex": ["[email protected]", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], "esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], @@ -720,6 +732,8 @@ "estree-walker": ["[email protected]", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], + "eventemitter3": ["[email protected]", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], + "expand-template": ["[email protected]", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], "fast-fifo": ["[email protected]", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], @@ -756,6 +770,8 @@ "get-caller-file": ["[email protected]", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["[email protected]", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "get-pkg-repo": ["[email protected]", "", { "dependencies": { "@hutson/parse-repository-url": "^3.0.0", "hosted-git-info": "^4.0.0", "through2": "^2.0.0", "yargs": "^16.2.0" }, "bin": { "get-pkg-repo": "src/cli.js" } }, "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA=="], "git-raw-commits": ["[email protected]", "", { "dependencies": { "dargs": "^7.0.0", "lodash": "^4.17.15", "meow": "^8.0.0", "split2": "^3.0.0", "through2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.js" } }, "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A=="], @@ -790,6 +806,8 @@ "hosted-git-info": ["[email protected]", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], + "husky": ["[email protected]", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], + "iconify-icon": ["[email protected]", "", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-DYPAumiUeUeT/GHT8x2wrAVKn1FqZJqFH0Y5pBefapWRreV1BBvqBVMb0020YQ2njmbR59r/IathL2d2OrDrxA=="], "ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -888,6 +906,10 @@ "lines-and-columns": ["[email protected]", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + "lint-staged": ["[email protected]", "", { "dependencies": { "commander": "^14.0.2", "listr2": "^9.0.5", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow=="], + + "listr2": ["[email protected]", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="], + "load-json-file": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.1.2", "parse-json": "^4.0.0", "pify": "^3.0.0", "strip-bom": "^3.0.0" } }, "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw=="], "locate-character": ["[email protected]", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], @@ -900,6 +922,8 @@ "lodash.merge": ["[email protected]", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "log-update": ["[email protected]", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + "lru-cache": ["[email protected]", "", {}, "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ=="], "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -918,6 +942,8 @@ "micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mimic-function": ["[email protected]", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + "mimic-response": ["[email protected]", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], "min-indent": ["[email protected]", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], @@ -944,6 +970,8 @@ "ms": ["[email protected]", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="], + "nano-spawn": ["[email protected]", "", {}, "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw=="], + "nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "napi-build-utils": ["[email protected]", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], @@ -972,6 +1000,8 @@ "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onetime": ["[email protected]", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "open": ["[email protected]", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], "oxfmt": ["[email protected]", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.35.0", "@oxfmt/binding-android-arm64": "0.35.0", "@oxfmt/binding-darwin-arm64": "0.35.0", "@oxfmt/binding-darwin-x64": "0.35.0", "@oxfmt/binding-freebsd-x64": "0.35.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.35.0", "@oxfmt/binding-linux-arm-musleabihf": "0.35.0", "@oxfmt/binding-linux-arm64-gnu": "0.35.0", "@oxfmt/binding-linux-arm64-musl": "0.35.0", "@oxfmt/binding-linux-ppc64-gnu": "0.35.0", "@oxfmt/binding-linux-riscv64-gnu": "0.35.0", "@oxfmt/binding-linux-riscv64-musl": "0.35.0", "@oxfmt/binding-linux-s390x-gnu": "0.35.0", "@oxfmt/binding-linux-x64-gnu": "0.35.0", "@oxfmt/binding-linux-x64-musl": "0.35.0", "@oxfmt/binding-openharmony-arm64": "0.35.0", "@oxfmt/binding-win32-arm64-msvc": "0.35.0", "@oxfmt/binding-win32-ia32-msvc": "0.35.0", "@oxfmt/binding-win32-x64-msvc": "0.35.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-QYeXWkP+aLt7utt5SLivNIk09glWx9QE235ODjgcEZ3sd1VMaUBSpLymh6ZRCA76gD2rMP4bXanUz/fx+nLM9Q=="], @@ -1010,6 +1040,8 @@ "picomatch": ["[email protected]", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "pidtree": ["[email protected]", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + "pify": ["[email protected]", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], "playwright": ["[email protected]", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], @@ -1070,8 +1102,12 @@ "resolve": ["[email protected]", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], + "restore-cursor": ["[email protected]", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + "reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + "rfdc": ["[email protected]", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + "rimraf": ["[email protected]", "", { "dependencies": { "glob": "^11.0.0", "package-json-from-dist": "^1.0.0" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A=="], "rolldown": ["[email protected]", "", { "dependencies": { "@oxc-project/types": "=0.111.0", "@rolldown/pluginutils": "1.0.0-rc.2" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.2", "@rolldown/binding-darwin-arm64": "1.0.0-rc.2", "@rolldown/binding-darwin-x64": "1.0.0-rc.2", "@rolldown/binding-freebsd-x64": "1.0.0-rc.2", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.2", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.2", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.2", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.2", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.2", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.2", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.2", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.2", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.2" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-1g/8Us9J8sgJGn3hZfBecX1z4U3y5KO7V/aV2U1M/9UUzLNqHA8RfFQ/NPT7HLxOIldyIgrcjaYTRvA81KhJIg=="], @@ -1144,6 +1180,8 @@ "streamx": ["[email protected]", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="], + "string-argv": ["[email protected]", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + "string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "string-width-cjs": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1290,6 +1328,8 @@ "yallist": ["[email protected]", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + "yaml": ["[email protected]", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + "yargs": ["[email protected]", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs-parser": ["[email protected]", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], @@ -1400,6 +1440,10 @@ "chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], + "cli-truncate/slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "cli-truncate/string-width": ["[email protected]", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="], + "conventional-changelog-writer/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "conventional-commits-parser/split2": ["[email protected]", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="], @@ -1436,8 +1480,18 @@ "hosted-git-info/lru-cache": ["[email protected]", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "lint-staged/commander": ["[email protected]", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], + + "listr2/wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "load-json-file/pify": ["[email protected]", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], + "log-update/slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "log-update/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "log-update/wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "meow/read-pkg-up": ["[email protected]", "", { "dependencies": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", "type-fest": "^0.8.1" } }, "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg=="], "meow/type-fest": ["[email protected]", "", {}, "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw=="], @@ -1474,6 +1528,8 @@ "replace/yargs": ["[email protected]", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + "restore-cursor/signal-exit": ["[email protected]", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "rollup/fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "sharp/node-addon-api": ["[email protected]", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], @@ -1544,6 +1600,12 @@ "chalk/ansi-styles/color-convert": ["[email protected]", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], + "cli-truncate/slice-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "cli-truncate/slice-ansi/is-fullwidth-code-point": ["[email protected]", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "cli-truncate/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "del/rimraf/glob": ["[email protected]", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "fs-minipass/minipass/yallist": ["[email protected]", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], @@ -1556,6 +1618,22 @@ "hosted-git-info/lru-cache/yallist": ["[email protected]", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "listr2/wrap-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "listr2/wrap-ansi/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "listr2/wrap-ansi/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "log-update/slice-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "log-update/slice-ansi/is-fullwidth-code-point": ["[email protected]", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "log-update/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "log-update/wrap-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "log-update/wrap-ansi/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "meow/read-pkg-up/read-pkg": ["[email protected]", "", { "dependencies": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", "parse-json": "^5.0.0", "type-fest": "^0.6.0" } }, "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg=="], "meow/read-pkg-up/type-fest": ["[email protected]", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], @@ -1610,12 +1688,20 @@ "chalk/ansi-styles/color-convert/color-name": ["[email protected]", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="], + "cli-truncate/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "del/rimraf/glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "get-pkg-repo/through2/readable-stream/safe-buffer": ["[email protected]", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "get-pkg-repo/through2/readable-stream/string_decoder": ["[email protected]", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "listr2/wrap-ansi/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "listr2/wrap-ansi/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "log-update/wrap-ansi/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "meow/read-pkg-up/read-pkg/normalize-package-data": ["[email protected]", "", { "dependencies": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], "meow/read-pkg-up/read-pkg/parse-json": ["[email protected]", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], diff --git a/lint-staged.config.js b/lint-staged.config.js new file mode 100644 index 00000000..ecf030de --- /dev/null +++ b/lint-staged.config.js @@ -0,0 +1,3 @@ +export default { + "*.{js,ts,svelte}": () => "oxfmt .", +}; diff --git a/package.json b/package.json index a5bdef2d..14e672b9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "test:e2e:ark:debug": "test/e2e/run.sh debug", "lint": "oxlint . && oxfmt --check .", "lint:fix": "oxlint --fix . && oxfmt .", - "format": "oxfmt ." + "format": "oxfmt .", + "prepare": "husky" }, "dependencies": { "@arkade-os/sdk": "^0.3.12", @@ -52,8 +53,10 @@ "daisyui": "^5.5.17", "date-fns": "^4.1.0", "esptool-js": "^0.5.7", + "husky": "^9.1.7", "iconify-icon": "^3.0.2", "js-cookie": "^3.0.5", + "lint-staged": "^16.2.7", "nostr-tools": "^2.23.0", "oxfmt": "^0.35.0", "oxlint": "^1.35.0", diff --git a/src/lib/ark.ts b/src/lib/ark.ts index a3939a40..82d1ccce 100644 --- a/src/lib/ark.ts +++ b/src/lib/ark.ts @@ -104,7 +104,11 @@ export const syncTransactions = async (aid: string) => { if (arkSending) return; const p = _syncTransactions(aid); syncPromise = p; - try { return await p; } finally { if (syncPromise === p) syncPromise = null; } + try { + return await p; + } finally { + if (syncPromise === p) syncPromise = null; + } }; export const vtxoKey = (v: { txid: string; vout: number }) => `${v.txid}:${v.vout}`; @@ -131,9 +135,7 @@ let syncPromise: Promise<any> | null = null; const withTimeout = <T>(promise: Promise<T>, ms: number, label: string) => Promise.race([ promise, - new Promise<T>((_, reject) => - setTimeout(() => reject(new Error(`${label} timed out`)), ms), - ), + new Promise<T>((_, reject) => setTimeout(() => reject(new Error(`${label} timed out`)), ms)), ]); export const sendArk = async (address: string, amount: number) => { @@ -158,21 +160,13 @@ export const sendArk = async (address: string, amount: number) => { } let txid; try { - txid = await withTimeout( - wallet.sendBitcoin({ address, amount }), - 45_000, - "Ark send", - ); + txid = await withTimeout(wallet.sendBitcoin({ address, amount }), 45_000, "Ark send"); } catch (e: any) { if (e?.message?.includes("VTXO_ALREADY_REGISTERED")) { await wallet.settle(); const vtxos = await wallet.getVtxos({ spendableOnly: false }); if (vtxos?.length) addProcessedVtxos(vtxos.map(vtxoKey)); - txid = await withTimeout( - wallet.sendBitcoin({ address, amount }), - 45_000, - "Ark send retry", - ); + txid = await withTimeout(wallet.sendBitcoin({ address, amount }), 45_000, "Ark send retry"); } else { throw e; } diff --git a/src/lib/crypto.ts b/src/lib/crypto.ts index d941d894..80a6364a 100644 --- a/src/lib/crypto.ts +++ b/src/lib/crypto.ts @@ -2,17 +2,11 @@ export function isPrfEncrypted(value: string): boolean { return value.startsWith("{"); } -async function importPrfKey( - prfKey: ArrayBuffer, - usages: KeyUsage[], -): Promise<CryptoKey> { +async function importPrfKey(prfKey: ArrayBuffer, usages: KeyUsage[]): Promise<CryptoKey> { return crypto.subtle.importKey("raw", prfKey, "AES-GCM", false, usages); } -export async function prfEncrypt( - prfKey: ArrayBuffer, - plaintext: Uint8Array, -): Promise<string> { +export async function prfEncrypt(prfKey: ArrayBuffer, plaintext: Uint8Array): Promise<string> { const key = await importPrfKey(prfKey, ["encrypt"]); const iv = crypto.getRandomValues(new Uint8Array(12)); const ciphertext = new Uint8Array( @@ -29,10 +23,7 @@ export async function prfEncrypt( }); } -export async function prfDecrypt( - prfKey: ArrayBuffer, - encrypted: string, -): Promise<Uint8Array> { +export async function prfDecrypt(prfKey: ArrayBuffer, encrypted: string): Promise<Uint8Array> { const { iv, ct } = JSON.parse(encrypted); const key = await importPrfKey(prfKey, ["decrypt"]); const ivBytes = fromBase64Url(iv); diff --git a/src/lib/nip07.ts b/src/lib/nip07.ts index a0af0861..093e2ead 100644 --- a/src/lib/nip07.ts +++ b/src/lib/nip07.ts @@ -1,102 +1,108 @@ -import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; -import { finalizeEvent } from 'nostr-tools/pure'; -import * as libnip44 from '$lib/nip44'; -import { u } from '$lib/nip44'; +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; +import { finalizeEvent } from "nostr-tools/pure"; +import * as libnip44 from "$lib/nip44"; +import { u } from "$lib/nip44"; -import { eventToSign, signer } from '$lib/store'; -import { get } from 'svelte/store'; +import { eventToSign, signer } from "$lib/store"; +import { get } from "svelte/store"; // Ensures we have access to a secret key, triggering the signer modal if needed. // Returns the sk as Uint8Array, or null if using NIP-07 extension. export const ensureSigner = async (userPubkey: string): Promise<Uint8Array | null> => { - // Check if NIP-07 extension is available and matches user - if (window.nostr) { - try { - const extPubkey = await window.nostr.getPublicKey(); - if (extPubkey === userPubkey) { - return null; // Use NIP-07 - } - } catch (e) { - // Extension didn't work, fall through to signer - } + // Check if NIP-07 extension is available and matches user + if (window.nostr) { + try { + const extPubkey = await window.nostr.getPublicKey(); + if (extPubkey === userPubkey) { + return null; // Use NIP-07 + } + } catch (e) { + // Extension didn't work, fall through to signer } + } - // Check if signer already has a valid sk (64 hex chars = 32 bytes) - const currentSigner = get(signer); - if (currentSigner?.ready && currentSigner?.params?.sk && currentSigner.params.sk.length === 64) { - return hexToBytes(currentSigner.params.sk); - } + // Check if signer already has a valid sk (64 hex chars = 32 bytes) + const currentSigner = get(signer); + if (currentSigner?.ready && currentSigner?.params?.sk && currentSigner.params.sk.length === 64) { + return hexToBytes(currentSigner.params.sk); + } - // Clear invalid signer state before triggering modal - if (currentSigner?.ready && (!currentSigner?.params?.sk || currentSigner.params.sk.length !== 64)) { - signer.set(undefined); - } + // Clear invalid signer state before triggering modal + if ( + currentSigner?.ready && + (!currentSigner?.params?.sk || currentSigner.params.sk.length !== 64) + ) { + signer.set(undefined); + } - // Trigger the signer modal with a dummy event - const dummyEvent = { kind: 14, content: 'DM encryption', tags: [], created_at: Math.floor(Date.now() / 1000) }; - eventToSign.set(dummyEvent); + // Trigger the signer modal with a dummy event + const dummyEvent = { + kind: 14, + content: "DM encryption", + tags: [], + created_at: Math.floor(Date.now() / 1000), + }; + eventToSign.set(dummyEvent); - // Wait for signer to be ready - const signerValue = await new Promise<any>((resolve, reject) => { - let unsubscribe: () => void; - unsubscribe = signer.subscribe((v) => { - if (v === 'cancel') { - setTimeout(() => unsubscribe?.()); - eventToSign.set(null); - reject(new Error('cancelled')); - } - if (v?.ready) { - setTimeout(() => unsubscribe?.()); - eventToSign.set(null); - resolve(v); - } - }); + // Wait for signer to be ready + const signerValue = await new Promise<any>((resolve, reject) => { + let unsubscribe: () => void; + unsubscribe = signer.subscribe((v) => { + if (v === "cancel") { + setTimeout(() => unsubscribe?.()); + eventToSign.set(null); + reject(new Error("cancelled")); + } + if (v?.ready) { + setTimeout(() => unsubscribe?.()); + eventToSign.set(null); + resolve(v); + } }); + }); - if (signerValue?.params?.sk) { - return hexToBytes(signerValue.params.sk); - } + if (signerValue?.params?.sk) { + return hexToBytes(signerValue.params.sk); + } - // If extension method was selected - if (signerValue?.method === 'extension') { - return null; // Use NIP-07 - } + // If extension method was selected + if (signerValue?.method === "extension") { + return null; // Use NIP-07 + } - throw new Error('No signing method available'); -} + throw new Error("No signing method available"); +}; // Signs an event using NIP-07 if available, otherwise getPrivateKey() export const signEvent = async (event: any, user: any): Promise<any> => { - const sk = await ensureSigner(user.pubkey); - if (sk === null) { - return (window as any).nostr.signEvent(event); - } else { - return finalizeEvent(event, sk); - } -} + const sk = await ensureSigner(user.pubkey); + if (sk === null) { + return (window as any).nostr.signEvent(event); + } else { + return finalizeEvent(event, sk); + } +}; // Encrypts an event in NIP-44 using NIP-07 if available, otherwise getPrivateKey() export const encrypt = async (json: any, user: any, receiverPK: string): Promise<string> => { - const senderSK = await ensureSigner(user.pubkey); - if (senderSK === null) { - return (window as any).nostr.nip44.encrypt( - receiverPK, JSON.stringify(json)); - } else { - const senderSKString = bytesToHex(senderSK); - const ckey = u.getConversationKey(senderSKString, receiverPK); - return libnip44.encrypt(JSON.stringify(json), ckey); - } -} + const senderSK = await ensureSigner(user.pubkey); + if (senderSK === null) { + return (window as any).nostr.nip44.encrypt(receiverPK, JSON.stringify(json)); + } else { + const senderSKString = bytesToHex(senderSK); + const ckey = u.getConversationKey(senderSKString, receiverPK); + return libnip44.encrypt(JSON.stringify(json), ckey); + } +}; // Decrypts an event in NIP-44 using NIP-07 if available, otherwise getPrivateKey() export const decrypt = async (encrypted: string, user: any, receiverPK: string): Promise<any> => { - const senderSK = await ensureSigner(user.pubkey); - if (senderSK === null) { - return JSON.parse(await (window as any).nostr.nip44.decrypt( - receiverPK, encrypted)); - } else { - const senderSKString = bytesToHex(senderSK); - const ckey = u.getConversationKey(senderSKString, receiverPK); - return JSON.parse(libnip44.decrypt(encrypted, ckey)); - } -} + const senderSK = await ensureSigner(user.pubkey); + if (senderSK === null) { + return JSON.parse(await (window as any).nostr.nip44.decrypt(receiverPK, encrypted)); + } else { + const senderSKString = bytesToHex(senderSK); + const ckey = u.getConversationKey(senderSKString, receiverPK); + return JSON.parse(libnip44.decrypt(encrypted, ckey)); + } +}; diff --git a/src/lib/nip17.ts b/src/lib/nip17.ts index 0398c426..97191a18 100644 --- a/src/lib/nip17.ts +++ b/src/lib/nip17.ts @@ -5,18 +5,18 @@ import { SimplePool } from "nostr-tools/pool"; import { Relay } from "nostr-tools/relay"; import { unwrapEvent } from "nostr-tools/nip17"; -import { ensureSigner } from '$lib/nip07'; -import { relaysSupporting } from '$lib/nip11'; -import { expired, expiration } from '$lib/nip40'; -import { encrypt, u } from '$lib/nip44'; +import { ensureSigner } from "$lib/nip07"; +import { relaysSupporting } from "$lib/nip11"; +import { expired, expiration } from "$lib/nip40"; +import { encrypt, u } from "$lib/nip44"; const TWO_DAYS = 2 * 24 * 60 * 60; const randomTimestamp = () => Math.floor(Date.now() / 1000 - Math.random() * TWO_DAYS); const pool = new SimplePool(); let relayListCache = new Map(); -import { PUBLIC_DM_RELAYS } from '$env/static/public'; -const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(','); +import { PUBLIC_DM_RELAYS } from "$env/static/public"; +const DM_RELAYS_LIST = PUBLIC_DM_RELAYS.split(","); const DM_FETCH_LIMIT = 65536; // Create a NIP-17 message using a provided sender secret key. @@ -283,7 +283,14 @@ const publishToPreferred = async ( } }; -const createAndPublishMessageEvent = async (sk: Uint8Array | null, message: string, userPK: string, recipientPK: string, eventFor: string, expiryDays?: number) => { +const createAndPublishMessageEvent = async ( + sk: Uint8Array | null, + message: string, + userPK: string, + recipientPK: string, + eventFor: string, + expiryDays?: number, +) => { let event; if (sk === null) { event = await createNIP17MessageNIP07(message, userPK, recipientPK, eventFor, expiryDays); @@ -303,12 +310,30 @@ export const send = async (message: string, user: any, recipient: any, expiryDay if (user.pubkey === recipient.pubkey) { return createAndPublishMessageEvent( - sk, message, user.pubkey, recipient.pubkey, user.pubkey, expiryDays); + sk, + message, + user.pubkey, + recipient.pubkey, + user.pubkey, + expiryDays, + ); } else { const p1 = createAndPublishMessageEvent( - sk, message, user.pubkey, recipient.pubkey, recipient.pubkey, expiryDays); + sk, + message, + user.pubkey, + recipient.pubkey, + recipient.pubkey, + expiryDays, + ); const p2 = createAndPublishMessageEvent( - sk, message, user.pubkey, recipient.pubkey, user.pubkey, expiryDays); + sk, + message, + user.pubkey, + recipient.pubkey, + user.pubkey, + expiryDays, + ); return Promise.all([p1, p2]); } }; diff --git a/src/lib/nip44.ts b/src/lib/nip44.ts index 6025a54f..19384b9b 100644 --- a/src/lib/nip44.ts +++ b/src/lib/nip44.ts @@ -24,7 +24,9 @@ export const u = { }, getConversationKey(privkeyA: string, pubkeyB: string): Uint8Array { - const sharedX = secp256k1.getSharedSecret(hexToBytes(privkeyA), hexToBytes("02" + pubkeyB)).subarray(1, 33); + const sharedX = secp256k1 + .getSharedSecret(hexToBytes(privkeyA), hexToBytes("02" + pubkeyB)) + .subarray(1, 33); return hkdf_extract(sha256, sharedX, utf8ToBytes("nip44-v2")); }, diff --git a/src/lib/walletEntropy.ts b/src/lib/walletEntropy.ts index f022a2cc..25bd49b8 100644 --- a/src/lib/walletEntropy.ts +++ b/src/lib/walletEntropy.ts @@ -10,8 +10,7 @@ const DERIVATION_EVENT = { async function hashSignature(sig: string): Promise<ArrayBuffer> { const sigBytes = new Uint8Array(sig.length / 2); - for (let i = 0; i < sig.length; i += 2) - sigBytes[i / 2] = parseInt(sig.slice(i, i + 2), 16); + for (let i = 0; i < sig.length; i += 2) sigBytes[i / 2] = parseInt(sig.slice(i, i + 2), 16); return crypto.subtle.digest("SHA-256", sigBytes); } diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.ts b/src/routes/(app)/ln/[lnurl]/+page.server.ts index eec5ce31..39bc0e72 100644 --- a/src/routes/(app)/ln/[lnurl]/+page.server.ts +++ b/src/routes/(app)/ln/[lnurl]/+page.server.ts @@ -12,7 +12,9 @@ export async function load({ params, parent }) { let data; const { lnurl } = params; - const url = Buffer.from(fromWords(decode(lnurl as `${string}1${string}`, 20000).words)).toString(); + const url = Buffer.from( + fromWords(decode(lnurl as `${string}1${string}`, 20000).words), + ).toString(); try { data = await get(`/decode?text=${lnurl}`); diff --git a/src/routes/(app)/payment/[id]/+page.svelte b/src/routes/(app)/payment/[id]/+page.svelte index 415581b6..84a728a0 100644 --- a/src/routes/(app)/payment/[id]/+page.svelte +++ b/src/routes/(app)/payment/[id]/+page.svelte @@ -144,7 +144,7 @@ <div> <span class="text-lg text-secondary">{$t("payments.date")}</span> <div> - {format(new Date(created), "MMM d, yyyy", { locale })} {format(new Date(created), "h:mmaaa", { locale })} + {format(new Date(created), "MMM d, yyyy", { locale })}{format(new Date(created), "h:mmaaa", { locale })} </div> </div> @@ -199,7 +199,10 @@ <span class="text-lg text-secondary">Txid</span> <div class="flex"> <div>{hash}</div> - <button class="flex font-bold hover:opacity-80 mb-auto my-auto" onclick={() => copy(hash)} aria-label="Copy transaction ID" + <button + class="flex font-bold hover:opacity-80 mb-auto my-auto" + onclick={() => copy(hash)} + aria-label="Copy transaction ID" ></button> </div> </div> @@ -218,7 +221,10 @@ {txid} </a> </div> - <button class="flex font-bold hover:opacity-80 mb-auto my-auto" onclick={() => copy(txid)} aria-label="Copy transaction ID" + <button + class="flex font-bold hover:opacity-80 mb-auto my-auto" + onclick={() => copy(txid)} + aria-label="Copy transaction ID" ></button> </div> </div> @@ -229,7 +235,10 @@ <span class="text-lg text-secondary">Path</span> <div class="flex"> <div>{path}</div> - <button class="flex font-bold hover:opacity-80 mb-auto my-auto" onclick={() => copy(path)} aria-label="Copy path" + <button + class="flex font-bold hover:opacity-80 mb-auto my-auto" + onclick={() => copy(path)} + aria-label="Copy path" ></button> </div> </div> diff --git a/src/routes/(static)/login/+page.server.ts b/src/routes/(static)/login/+page.server.ts index 18f9cb20..4200bc01 100644 --- a/src/routes/(static)/login/+page.server.ts +++ b/src/routes/(static)/login/+page.server.ts @@ -42,8 +42,7 @@ export const actions = { passkey: async ({ cookies, fetch, request }) => { const form = await fd(request); let { credential, challengeId, loginRedirect } = form; - if (loginRedirect === "null" || loginRedirect === "undefined") - loginRedirect = undefined; + if (loginRedirect === "null" || loginRedirect === "undefined") loginRedirect = undefined; credential = JSON.parse(credential); const maxAge = 380 * 24 * 60 * 60; diff --git a/svelte.config.js b/svelte.config.js index ef65bfeb..ae2de4cb 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -18,7 +18,13 @@ const config = { }, }, onwarn: (warning, handler) => { - if (warning.code.includes("caption") || warning.filename.includes("Toast") || warning.code === "css_unknown_at_rule" || warning.code === "state_referenced_locally") return; + if ( + warning.code.includes("caption") || + warning.filename.includes("Toast") || + warning.code === "css_unknown_at_rule" || + warning.code === "state_referenced_locally" + ) + return; handler(warning); }, }; diff --git a/test/e2e/ark-custodial-receive.spec.ts b/test/e2e/ark-custodial-receive.spec.ts index d50cda55..41b63733 100644 --- a/test/e2e/ark-custodial-receive.spec.ts +++ b/test/e2e/ark-custodial-receive.spec.ts @@ -9,9 +9,7 @@ import { sendArkFromTestEndpoint, } from "./helpers"; -test("custodial account receives ark payment via vault forward", async ({ - page, -}) => { +test("custodial account receives ark payment via vault forward", async ({ page }) => { test.setTimeout(120_000); test.skip(!testSecret, "E2E_TEST_SECRET is required"); @@ -20,9 +18,7 @@ test("custodial account receives ark payment via vault forward", async ({ // 2. Create ark invoice via the vault account (needs wallet unlock) const { invoiceId, address } = await createArkInvoiceViaUI(page, bobPassword); - console.log( - `[e2e] Created ark invoice ${invoiceId} on vault account, address: ${address}`, - ); + console.log(`[e2e] Created ark invoice ${invoiceId} on vault account, address: ${address}`); // 3. Stay on invoice page (subscribe to websocket notifications) await page.goto(`/invoice/${invoiceId}`); @@ -39,9 +35,7 @@ test("custodial account receives ark payment via vault forward", async ({ let status: any; for (let i = 0; i < 60; i++) { - const statusResponse = await page.request.get( - `${apiBaseUrl}/invoice/${invoiceId}`, - ); + const statusResponse = await page.request.get(`${apiBaseUrl}/invoice/${invoiceId}`); if (statusResponse.ok()) { status = await statusResponse.json(); if ((status?.received || 0) >= 1000) { @@ -52,10 +46,7 @@ test("custodial account receives ark payment via vault forward", async ({ await page.waitForTimeout(2000); } - expect( - paid, - `Custodial ark forward did not complete: ${JSON.stringify(status)}`, - ).toBeTruthy(); + expect(paid, `Custodial ark forward did not complete: ${JSON.stringify(status)}`).toBeTruthy(); console.log( `[e2e] Custodial ark receive complete: invoice ${invoiceId} received=${status?.received}`, diff --git a/test/e2e/ark-send-custodial.spec.ts b/test/e2e/ark-send-custodial.spec.ts index 881a7f08..5806ca72 100644 --- a/test/e2e/ark-send-custodial.spec.ts +++ b/test/e2e/ark-send-custodial.spec.ts @@ -24,10 +24,7 @@ test("custodial sends to external ark address", async ({ browser }) => { alicePassword, ); - const { address: arkAddress } = await createArkInvoiceViaUI( - alicePage, - alicePassword, - ); + const { address: arkAddress } = await createArkInvoiceViaUI(alicePage, alicePassword); console.log(`[e2e] Alice ark address: ${arkAddress}`); await aliceContext.close(); diff --git a/test/e2e/custodial-bitcoin-send.spec.ts b/test/e2e/custodial-bitcoin-send.spec.ts index 8b4c8be9..c8543dac 100644 --- a/test/e2e/custodial-bitcoin-send.spec.ts +++ b/test/e2e/custodial-bitcoin-send.spec.ts @@ -21,8 +21,10 @@ test("custodial-to-custodial via bitcoin address sends internally and notifies r alicePassword, ); - const { invoiceId, address: bitcoinAddress } = - await createBitcoinInvoiceViaUI(alicePage, aliceUsername); + const { invoiceId, address: bitcoinAddress } = await createBitcoinInvoiceViaUI( + alicePage, + aliceUsername, + ); console.log(`[e2e] Alice custodial bitcoin address: ${bitcoinAddress}`); console.log(`[e2e] Alice invoice ID: ${invoiceId}`); @@ -43,14 +45,8 @@ test("custodial-to-custodial via bitcoin address sends internally and notifies r console.log(`[e2e] Bob navigated to: ${bobUrl}`); // Verify it went to /send/invoice/ (internal send via original invoice) - expect( - bobUrl, - `Expected /send/invoice/ URL but got: ${bobUrl}`, - ).toContain("/send/invoice/"); - expect( - bobUrl, - `Should NOT redirect to /pay/ but got: ${bobUrl}`, - ).not.toContain("/pay/"); + expect(bobUrl, `Expected /send/invoice/ URL but got: ${bobUrl}`).toContain("/send/invoice/"); + expect(bobUrl, `Should NOT redirect to /pay/ but got: ${bobUrl}`).not.toContain("/pay/"); // Bob should see the Numpad (no amount set on the invoice) const amountInput = bobPage.locator('[aria-label="Amount input"]'); @@ -89,7 +85,9 @@ test("custodial-to-custodial via bitcoin address sends internally and notifies r // Verify Alice sees the payment success page const successText = await alicePage.locator("h1").first().innerText({ timeout: 5_000 }); expect( - successText.toLowerCase().includes("payment") || successText.toLowerCase().includes("success") || successText.toLowerCase().includes("detected"), + successText.toLowerCase().includes("payment") || + successText.toLowerCase().includes("success") || + successText.toLowerCase().includes("detected"), `Expected payment success text, got: ${successText}`, ).toBeTruthy(); diff --git a/test/e2e/custodial-receive-bitcoin.spec.ts b/test/e2e/custodial-receive-bitcoin.spec.ts index 11ccd3be..934f76a7 100644 --- a/test/e2e/custodial-receive-bitcoin.spec.ts +++ b/test/e2e/custodial-receive-bitcoin.spec.ts @@ -10,18 +10,13 @@ import { waitForPaidRedirect, } from "./helpers"; -test("custodial receives external bitcoin payment from bitcoind", async ({ - page, -}) => { +test("custodial receives external bitcoin payment from bitcoind", async ({ page }) => { test.setTimeout(120_000); // --- Alice: log in and create bitcoin invoice --- await login(page, aliceUsername, alicePassword); - const { invoiceId, address } = await createBitcoinInvoiceViaUI( - page, - aliceUsername, - ); + const { invoiceId, address } = await createBitcoinInvoiceViaUI(page, aliceUsername); console.log(`[e2e] Alice bitcoin address: ${address}`); console.log(`[e2e] Alice invoice ID: ${invoiceId}`); @@ -40,18 +35,13 @@ test("custodial receives external bitcoin payment from bitcoind", async ({ interval: 2000, maxAttempts: 30, }); - console.log( - `[e2e] Invoice paid: received=${status?.received}, pending=${status?.pending}`, - ); + console.log(`[e2e] Invoice paid: received=${status?.received}, pending=${status?.pending}`); // --- Alice: should be redirected to /paid --- await waitForPaidRedirect(page, invoiceId, 30_000); console.log(`[e2e] Alice redirected to paid: ${page.url()}`); - const successText = await page - .locator("h1") - .first() - .innerText({ timeout: 5_000 }); + const successText = await page.locator("h1").first().innerText({ timeout: 5_000 }); expect( successText.toLowerCase().includes("payment") || successText.toLowerCase().includes("success") || diff --git a/test/e2e/custodial-receive-lightning.spec.ts b/test/e2e/custodial-receive-lightning.spec.ts index 545ea24e..5c35b557 100644 --- a/test/e2e/custodial-receive-lightning.spec.ts +++ b/test/e2e/custodial-receive-lightning.spec.ts @@ -9,9 +9,7 @@ import { waitForPaidRedirect, } from "./helpers"; -test("custodial receives external lightning payment from clb", async ({ - page, -}) => { +test("custodial receives external lightning payment from clb", async ({ page }) => { test.setTimeout(90_000); // --- Alice: log in and create a lightning invoice with amount via API --- @@ -35,15 +33,11 @@ test("custodial receives external lightning payment from clb", async ({ expect(invoiceId).toBeTruthy(); // Get the bolt11 payreq - const invDetailRes = await page.request.get( - `${apiBaseUrl}/invoice/${invoiceId}`, - ); + const invDetailRes = await page.request.get(`${apiBaseUrl}/invoice/${invoiceId}`); const invDetail = await invDetailRes.json(); const bolt11 = invDetail.text; expect(bolt11).toBeTruthy(); - console.log( - `[e2e] Alice lightning invoice: ${invoiceId}, bolt11: ${bolt11.substring(0, 40)}...`, - ); + console.log(`[e2e] Alice lightning invoice: ${invoiceId}, bolt11: ${bolt11.substring(0, 40)}...`); // Navigate to the invoice page (Alice subscribes to websocket notifications) await page.goto(`/invoice/${invoiceId}`); @@ -62,25 +56,17 @@ test("custodial receives external lightning payment from clb", async ({ interval: 1000, maxAttempts: 15, }); - console.log( - `[e2e] Invoice paid: received=${status?.received}, pending=${status?.pending}`, - ); + console.log(`[e2e] Invoice paid: received=${status?.received}, pending=${status?.pending}`); // --- Alice: wait for redirect to /paid (with fallback) --- const paidReached = await page - .waitForURL( - new RegExp(`/invoice/${invoiceId}/paid(?:[/?#]|$)`), - { timeout: 10_000 }, - ) + .waitForURL(new RegExp(`/invoice/${invoiceId}/paid(?:[/?#]|$)`), { timeout: 10_000 }) .then(() => true) .catch(() => false); if (paidReached) { console.log(`[e2e] Alice redirected to paid: ${page.url()}`); - const successText = await page - .locator("h1") - .first() - .innerText({ timeout: 5_000 }); + const successText = await page.locator("h1").first().innerText({ timeout: 5_000 }); expect( successText.toLowerCase().includes("payment") || successText.toLowerCase().includes("success") || diff --git a/test/e2e/custodial-send-external-btc.spec.ts b/test/e2e/custodial-send-external-btc.spec.ts index f03aa81e..a19e3dea 100644 --- a/test/e2e/custodial-send-external-btc.spec.ts +++ b/test/e2e/custodial-send-external-btc.spec.ts @@ -9,9 +9,7 @@ import { waitForSentRedirect, } from "./helpers"; -test("custodial sends to external bitcoin address (bitcoind)", async ({ - browser, -}) => { +test("custodial sends to external bitcoin address (bitcoind)", async ({ browser }) => { test.setTimeout(90_000); // --- Get external bitcoin address from 'external' wallet (NOT 'coinos' hot wallet) --- diff --git a/test/e2e/custodial-send-external-ln.spec.ts b/test/e2e/custodial-send-external-ln.spec.ts index 34e14c65..6142d9c8 100644 --- a/test/e2e/custodial-send-external-ln.spec.ts +++ b/test/e2e/custodial-send-external-ln.spec.ts @@ -9,9 +9,7 @@ import { waitForSentRedirect, } from "./helpers"; -test("custodial sends to external lightning node (clb) via bolt11", async ({ - browser, -}) => { +test("custodial sends to external lightning node (clb) via bolt11", async ({ browser }) => { test.setTimeout(90_000); // --- Create bolt11 on external node (clb) --- diff --git a/test/e2e/custodial-send-lightning.spec.ts b/test/e2e/custodial-send-lightning.spec.ts index bddff32a..885da1ae 100644 --- a/test/e2e/custodial-send-lightning.spec.ts +++ b/test/e2e/custodial-send-lightning.spec.ts @@ -42,15 +42,11 @@ test("custodial-to-custodial via lightning bolt11 sends internally and notifies expect(invoiceId).toBeTruthy(); // Get the bolt11 payreq - const invDetailRes = await alicePage.request.get( - `${apiBaseUrl}/invoice/${invoiceId}`, - ); + const invDetailRes = await alicePage.request.get(`${apiBaseUrl}/invoice/${invoiceId}`); const invDetail = await invDetailRes.json(); const bolt11 = invDetail.text; expect(bolt11).toBeTruthy(); - console.log( - `[e2e] Alice bolt11 invoice: ${invoiceId}, bolt11: ${bolt11.substring(0, 40)}...`, - ); + console.log(`[e2e] Alice bolt11 invoice: ${invoiceId}, bolt11: ${bolt11.substring(0, 40)}...`); // Alice navigates to invoice page (subscribes to websocket notifications) await alicePage.goto(`/invoice/${invoiceId}`); @@ -76,10 +72,7 @@ test("custodial-to-custodial via lightning bolt11 sends internally and notifies await noTipButton.click(); // After skipping tip, redirects to /invoice/{id} → then to /send/invoice/{id} - await bobPage.waitForURL( - (url) => !url.pathname.includes("/tip"), - { timeout: 15_000 }, - ); + await bobPage.waitForURL((url) => !url.pathname.includes("/tip"), { timeout: 15_000 }); bobUrl = bobPage.url(); console.log(`[e2e] After tip skip: ${bobUrl}`); } @@ -114,10 +107,7 @@ test("custodial-to-custodial via lightning bolt11 sends internally and notifies await waitForPaidRedirect(alicePage, invoiceId, 30_000); console.log(`[e2e] Alice redirected to: ${alicePage.url()}`); - const successText = await alicePage - .locator("h1") - .first() - .innerText({ timeout: 5_000 }); + const successText = await alicePage.locator("h1").first().innerText({ timeout: 5_000 }); expect( successText.toLowerCase().includes("payment") || successText.toLowerCase().includes("success") || diff --git a/test/e2e/custodial-send-username.spec.ts b/test/e2e/custodial-send-username.spec.ts index 8dff65ab..f8b4a002 100644 --- a/test/e2e/custodial-send-username.spec.ts +++ b/test/e2e/custodial-send-username.spec.ts @@ -54,10 +54,7 @@ test("custodial-to-custodial via username (/pay/) sends and notifies recipient", // The form creates an invoice. If Alice has tip prompt, we go to /invoice/{id}/tip // Otherwise we go to /invoice/{id} which loads the send page - await bobPage.waitForURL( - (url) => !url.pathname.startsWith("/pay/"), - { timeout: 15_000 }, - ); + await bobPage.waitForURL((url) => !url.pathname.startsWith("/pay/"), { timeout: 15_000 }); const afterPayUrl = bobPage.url(); console.log(`[e2e] After /pay/ submit: ${afterPayUrl}`); @@ -71,10 +68,7 @@ test("custodial-to-custodial via username (/pay/) sends and notifies recipient", // After skipping tip, we get redirected to /invoice/{id} // which should then load as /send/invoice/{id} for the payer - await bobPage.waitForURL( - (url) => !url.pathname.includes("/tip"), - { timeout: 15_000 }, - ); + await bobPage.waitForURL((url) => !url.pathname.includes("/tip"), { timeout: 15_000 }); console.log(`[e2e] After tip skip: ${bobPage.url()}`); } diff --git a/test/e2e/helpers.ts b/test/e2e/helpers.ts index 1d85825a..08d19810 100644 --- a/test/e2e/helpers.ts +++ b/test/e2e/helpers.ts @@ -8,8 +8,7 @@ export const alicePassword = process.env.E2E_ALICE_PASSWORD || "pw"; export const bobUsername = process.env.E2E_BOB_USERNAME || "bob"; export const bobPassword = process.env.E2E_BOB_PASSWORD || "pw"; export const testSecret = process.env.E2E_TEST_SECRET; -export const apiBaseUrl = - process.env.E2E_API_BASE_URL || "http://localhost:3119"; +export const apiBaseUrl = process.env.E2E_API_BASE_URL || "http://localhost:3119"; export const pauseAtEnd = process.env.E2E_PAUSE_AT_END === "true"; // --- Login helpers --- @@ -32,11 +31,7 @@ export async function login(page: Page, username: string, password: string) { throw new Error(`Login failed for ${username} after 5 attempts`); } -export async function loginNewContext( - browser: Browser, - username: string, - password: string, -) { +export async function loginNewContext(browser: Browser, username: string, password: string) { const context = await browser.newContext(); const page = await context.newPage(); await login(page, username, password); @@ -66,20 +61,14 @@ export async function apiGetInvoice(page: Page, invoiceId: string) { // --- Invoice creation via UI --- -export async function createBitcoinInvoiceViaUI( - page: Page, - username: string, -) { +export async function createBitcoinInvoiceViaUI(page: Page, username: string) { await page.goto(`/${username}/receive?address_type=bech32`); await page.waitForURL(/\/invoice\/[^/?#]+/, { timeout: 15_000 }); const invoiceId = page.url().match(/\/invoice\/([^/?#]+)/)?.[1]; expect(invoiceId, "Should have invoice ID in URL").toBeTruthy(); - const invoiceText = await page - .getByTestId("invoice-text") - .first() - .innerText({ timeout: 10_000 }); + const invoiceText = await page.getByTestId("invoice-text").first().innerText({ timeout: 10_000 }); const address = invoiceText.trim().replace(/\s+/g, ""); expect( address.startsWith("bcrt1") || address.startsWith("bc1"), @@ -89,10 +78,7 @@ export async function createBitcoinInvoiceViaUI( return { invoiceId: invoiceId!, address }; } -export async function createLightningInvoiceViaUI( - page: Page, - username: string, -) { +export async function createLightningInvoiceViaUI(page: Page, username: string) { await page.goto(`/${username}/receive`); await page.waitForURL(/\/invoice\/[^/?#]+/, { timeout: 15_000 }); @@ -118,8 +104,7 @@ export async function createLightningInvoiceViaUI( const bolt11 = invoiceText.replace(/\s+/g, ""); expect( - bolt11.toLowerCase().startsWith("lnbcrt") || - bolt11.toLowerCase().startsWith("lnbc"), + bolt11.toLowerCase().startsWith("lnbcrt") || bolt11.toLowerCase().startsWith("lnbc"), `Expected bolt11, got: ${bolt11.substring(0, 30)}`, ).toBeTruthy(); @@ -127,9 +112,7 @@ export async function createLightningInvoiceViaUI( } export async function createArkInvoiceViaUI(page: Page, password: string) { - const arkCard = page.locator( - '[data-testid="account-card"][data-account-type="ark"]', - ); + const arkCard = page.locator('[data-testid="account-card"][data-account-type="ark"]'); await expect(arkCard.first()).toBeVisible({ timeout: 10_000 }); const walletPassInput = page.getByTestId("walletpass-input"); @@ -140,10 +123,7 @@ export async function createArkInvoiceViaUI(page: Page, password: string) { const card = arkCard.nth(i); await card.scrollIntoViewIfNeeded(); - const accountHref = await card - .locator('a[href^="/account/"]') - .first() - .getAttribute("href"); + const accountHref = await card.locator('a[href^="/account/"]').first().getAttribute("href"); const accountId = accountHref?.split("/account/")[1]; await card.locator('a[href="/invoice"]').first().click(); @@ -188,9 +168,7 @@ export async function createArkInvoiceViaUI(page: Page, password: string) { expect(invoiceOpened).toBeTruthy(); - const invoiceText = ( - await page.getByTestId("invoice-text").first().innerText() - ).trim(); + const invoiceText = (await page.getByTestId("invoice-text").first().innerText()).trim(); const addressMatch = invoiceText.match(/t?ark1[a-z0-9]+/i); expect(addressMatch).toBeTruthy(); @@ -201,18 +179,13 @@ export async function createArkInvoiceViaUI(page: Page, password: string) { } export async function createVaultBitcoinInvoiceViaUI(page: Page) { - const vaultCard = page.locator( - '[data-testid="account-card"][data-account-type="bitcoin"]', - ); + const vaultCard = page.locator('[data-testid="account-card"][data-account-type="bitcoin"]'); await expect(vaultCard.first()).toBeVisible({ timeout: 10_000 }); await vaultCard.first().getByTestId("account-receive").click(); await page.waitForURL(/\/invoice\/[^/?#]+/, { timeout: 10_000 }); - const invoiceText = await page - .getByTestId("invoice-text") - .first() - .innerText({ timeout: 10_000 }); + const invoiceText = await page.getByTestId("invoice-text").first().innerText({ timeout: 10_000 }); const address = invoiceText.trim().replace(/\s+/g, ""); expect( address.startsWith("bcrt1") || address.startsWith("bc1"), @@ -237,10 +210,9 @@ export async function pasteAndSend(page: Page, text: string) { await page.locator('button[type="submit"]').first().click(); // Wait for navigation away from /send - await page.waitForURL( - (url) => url.pathname !== "/send" && url.pathname !== "/send/", - { timeout: 15_000 }, - ); + await page.waitForURL((url) => url.pathname !== "/send" && url.pathname !== "/send/", { + timeout: 15_000, + }); } export async function fillNumpadAmount(page: Page, amount: number) { @@ -265,15 +237,8 @@ export async function waitForSentRedirect(page: Page, timeout = 15_000) { await page.waitForURL(/\/sent\//, { timeout }); } -export async function waitForPaidRedirect( - page: Page, - invoiceId: string, - timeout = 30_000, -) { - await page.waitForURL( - new RegExp(`/invoice/${invoiceId}/paid(?:[/?#]|$)`), - { timeout }, - ); +export async function waitForPaidRedirect(page: Page, invoiceId: string, timeout = 30_000) { + await page.waitForURL(new RegExp(`/invoice/${invoiceId}/paid(?:[/?#]|$)`), { timeout }); } // --- Docker helpers --- @@ -290,18 +255,12 @@ export function lightningPay(bolt11: string): string { } export function lightningInvoice(amountMsat: number, label: string): string { - const result = dockerExec( - "clb", - `lightning-cli invoice ${amountMsat} "${label}" "${label}"`, - ); + const result = dockerExec("clb", `lightning-cli invoice ${amountMsat} "${label}" "${label}"`); return JSON.parse(result).bolt11 as string; } export function getBitcoinAddress(): string { - return dockerExec( - "bc", - "bitcoin-cli -regtest -rpcwallet=external getnewaddress", - ); + return dockerExec("bc", "bitcoin-cli -regtest -rpcwallet=external getnewaddress"); } export function bitcoinSend(address: string, amountBtc: string): string { @@ -312,14 +271,8 @@ export function bitcoinSend(address: string, amountBtc: string): string { } export function mineBlocks(n: number): void { - const minerAddr = dockerExec( - "bc", - "bitcoin-cli -regtest -rpcwallet=coinos getnewaddress", - ); - dockerExec( - "bc", - `bitcoin-cli -regtest -rpcwallet=coinos generatetoaddress ${n} "${minerAddr}"`, - ); + const minerAddr = dockerExec("bc", "bitcoin-cli -regtest -rpcwallet=coinos getnewaddress"); + dockerExec("bc", `bitcoin-cli -regtest -rpcwallet=coinos generatetoaddress ${n} "${minerAddr}"`); } // --- Polling helpers --- @@ -335,10 +288,7 @@ export async function waitForInvoicePaid( const res = await page.request.get(`${apiBaseUrl}/invoice/${invoiceId}`); if (res.ok()) { status = await res.json(); - if ( - (status?.received || 0) >= amount || - (status?.pending || 0) >= amount - ) { + if ((status?.received || 0) >= amount || (status?.pending || 0) >= amount) { return status; } } diff --git a/test/e2e/vault-receive-bitcoin.spec.ts b/test/e2e/vault-receive-bitcoin.spec.ts index e1be643a..013bb562 100644 --- a/test/e2e/vault-receive-bitcoin.spec.ts +++ b/test/e2e/vault-receive-bitcoin.spec.ts @@ -10,9 +10,7 @@ import { waitForPaidRedirect, } from "./helpers"; -test("bitcoin vault receives external bitcoin payment from bitcoind", async ({ - page, -}) => { +test("bitcoin vault receives external bitcoin payment from bitcoind", async ({ page }) => { test.setTimeout(120_000); // --- Alice: log in and create vault bitcoin invoice --- @@ -37,9 +35,7 @@ test("bitcoin vault receives external bitcoin payment from bitcoind", async ({ interval: 2000, maxAttempts: 30, }); - console.log( - `[e2e] Vault invoice paid: received=${status?.received}, pending=${status?.pending}`, - ); + console.log(`[e2e] Vault invoice paid: received=${status?.received}, pending=${status?.pending}`); // --- Alice: should be redirected to /paid --- const paidReached = await page @@ -52,9 +48,7 @@ test("bitcoin vault receives external bitcoin payment from bitcoind", async ({ if (paidReached) { console.log(`[e2e] Alice redirected to paid: ${page.url()}`); } else { - console.log( - `[e2e] No /paid redirect (vault may not auto-redirect), current: ${page.url()}`, - ); + console.log(`[e2e] No /paid redirect (vault may not auto-redirect), current: ${page.url()}`); } // Vault payments may show as pending — verify the invoice API shows received/pending diff --git a/test/e2e/vault-send-bitcoin.spec.ts b/test/e2e/vault-send-bitcoin.spec.ts index bd3d7172..bfb69f3b 100644 --- a/test/e2e/vault-send-bitcoin.spec.ts +++ b/test/e2e/vault-send-bitcoin.spec.ts @@ -8,9 +8,7 @@ import { pasteAndSend, } from "./helpers"; -test("pasting vault bitcoin address redirects to /send/bitcoin, not /pay", async ({ - browser, -}) => { +test("pasting vault bitcoin address redirects to /send/bitcoin, not /pay", async ({ browser }) => { test.setTimeout(60_000); // --- Alice: generate a vault invoice and grab the address --- @@ -21,24 +19,17 @@ test("pasting vault bitcoin address redirects to /send/bitcoin, not /pay", async ); // Find the bitcoin vault account card and click Receive - const vaultCard = alicePage.locator( - '[data-testid="account-card"][data-account-type="bitcoin"]', - ); + const vaultCard = alicePage.locator('[data-testid="account-card"][data-account-type="bitcoin"]'); await expect(vaultCard.first()).toBeVisible({ timeout: 10_000 }); - const receiveBtn = vaultCard - .first() - .getByTestId("account-receive"); + const receiveBtn = vaultCard.first().getByTestId("account-receive"); await receiveBtn.click(); // Wait for the invoice page await alicePage.waitForURL(/\/invoice\/[^/?#]+/, { timeout: 10_000 }); // Grab the bitcoin address from the invoice text - const invoiceText = await alicePage - .getByTestId("invoice-text") - .first() - .innerText(); + const invoiceText = await alicePage.getByTestId("invoice-text").first().innerText(); const vaultAddress = invoiceText.trim().replace(/\s+/g, ""); expect( vaultAddress.startsWith("bcrt1") || vaultAddress.startsWith("bc1"), @@ -62,14 +53,8 @@ test("pasting vault bitcoin address redirects to /send/bitcoin, not /pay", async console.log(`[e2e] After paste, Bob navigated to: ${finalUrl}`); // Should go to /send/bitcoin, NOT /pay/alice - expect( - finalUrl, - `Expected /send/bitcoin/ URL but got: ${finalUrl}`, - ).toContain("/send/bitcoin/"); - expect( - finalUrl, - `Should NOT redirect to /pay/ but got: ${finalUrl}`, - ).not.toContain("/pay/"); + expect(finalUrl, `Expected /send/bitcoin/ URL but got: ${finalUrl}`).toContain("/send/bitcoin/"); + expect(finalUrl, `Should NOT redirect to /pay/ but got: ${finalUrl}`).not.toContain("/pay/"); await bobContext.close(); }); diff --git a/test/e2e/vault-send-external-btc.spec.ts b/test/e2e/vault-send-external-btc.spec.ts index 01a64716..c8431b59 100644 --- a/test/e2e/vault-send-external-btc.spec.ts +++ b/test/e2e/vault-send-external-btc.spec.ts @@ -10,9 +10,7 @@ import { setActiveAccount, } from "./helpers"; -test("bitcoin vault sends to external bitcoin address with PSBT signing", async ({ - page, -}) => { +test("bitcoin vault sends to external bitcoin address with PSBT signing", async ({ page }) => { test.setTimeout(120_000); // Capture console errors for debugging @@ -30,9 +28,7 @@ test("bitcoin vault sends to external bitcoin address with PSBT signing", async await login(page, aliceUsername, alicePassword); // --- Find the bitcoin vault account and switch to it --- - const vaultCard = page.locator( - '[data-testid="account-card"][data-account-type="bitcoin"]', - ); + const vaultCard = page.locator('[data-testid="account-card"][data-account-type="bitcoin"]'); const vaultCount = await vaultCard.count(); if (vaultCount === 0) { test.skip(true, "No bitcoin vault account found"); @@ -81,9 +77,7 @@ test("bitcoin vault sends to external bitcoin address with PSBT signing", async } // Click Send — this triggers the WalletPass dialog for vault accounts - const sendButton = page.locator( - 'button[type="submit"]:not([data-testid="walletpass-submit"])', - ); + const sendButton = page.locator('button[type="submit"]:not([data-testid="walletpass-submit"])'); await expect(sendButton).toBeVisible({ timeout: 5_000 }); await sendButton.click(); @@ -130,16 +124,12 @@ test("bitcoin vault sends to external bitcoin address with PSBT signing", async // Client-side PSBT signing may fail if esplora/PSBT format is not // properly configured in the test environment const signingError = consoleErrors.some( - (e) => - e.includes("invalid tag") || - e.includes("decrypt") || - e.includes("PSBT"), + (e) => e.includes("invalid tag") || e.includes("decrypt") || e.includes("PSBT"), ); if (signingError) { test.skip( true, - "PSBT signing failed (likely test env issue): " + - consoleErrors.join("; "), + "PSBT signing failed (likely test env issue): " + consoleErrors.join("; "), ); return; } @@ -148,9 +138,7 @@ test("bitcoin vault sends to external bitcoin address with PSBT signing", async } } } else { - console.log( - "[e2e] No WalletPass (cached password), waiting for redirect...", - ); + console.log("[e2e] No WalletPass (cached password), waiting for redirect..."); await waitForSentRedirect(page, 30_000); } diff --git a/test/e2e/vault-send-to-custodial-btc.spec.ts b/test/e2e/vault-send-to-custodial-btc.spec.ts index 7ee522d2..aa52213c 100644 --- a/test/e2e/vault-send-to-custodial-btc.spec.ts +++ b/test/e2e/vault-send-to-custodial-btc.spec.ts @@ -39,9 +39,7 @@ test("vault sending to custodial bitcoin address stays on /send/bitcoin, not /se const { id: invoiceId } = await invoiceRes.json(); expect(invoiceId).toBeTruthy(); - const invDetail = await ( - await bobPage.request.get(`${apiBaseUrl}/invoice/${invoiceId}`) - ).json(); + const invDetail = await (await bobPage.request.get(`${apiBaseUrl}/invoice/${invoiceId}`)).json(); const custodialAddress = invDetail.text; expect(custodialAddress).toBeTruthy(); console.log(`[e2e] Bob custodial bitcoin address: ${custodialAddress}`); @@ -55,9 +53,7 @@ test("vault sending to custodial bitcoin address stays on /send/bitcoin, not /se ); // Find the bitcoin vault account and switch to it - const vaultCard = alicePage.locator( - '[data-testid="account-card"][data-account-type="bitcoin"]', - ); + const vaultCard = alicePage.locator('[data-testid="account-card"][data-account-type="bitcoin"]'); const vaultCount = await vaultCard.count(); if (vaultCount === 0) { test.skip(true, "No bitcoin vault account found"); @@ -83,14 +79,10 @@ test("vault sending to custodial bitcoin address stays on /send/bitcoin, not /se console.log(`[e2e] After paste, Alice navigated to: ${finalUrl}`); // Should stay on /send/bitcoin (on-chain), NOT redirect to /send/invoice (internal) - expect( - finalUrl, - `Expected /send/bitcoin/ URL but got: ${finalUrl}`, - ).toContain("/send/bitcoin/"); - expect( - finalUrl, - `Should NOT redirect to /send/invoice/ but got: ${finalUrl}`, - ).not.toContain("/send/invoice/"); + expect(finalUrl, `Expected /send/bitcoin/ URL but got: ${finalUrl}`).toContain("/send/bitcoin/"); + expect(finalUrl, `Should NOT redirect to /send/invoice/ but got: ${finalUrl}`).not.toContain( + "/send/invoice/", + ); await aliceContext.close(); }); |
