From: Chris Duncan Date: Wed, 17 Sep 2025 22:20:01 +0000 (-0700) Subject: Require Ledger dependency instead of making it optional. Update consuming classes... X-Git-Tag: v0.10.5~12^2~42 X-Git-Url: https://git.codecow.com/?a=commitdiff_plain;h=8a74842c0e6ba37ffb513e85bb45b858764ef3c1;p=libnemo.git Require Ledger dependency instead of making it optional. Update consuming classes as needed. Start fixing device event listeners. --- diff --git a/esbuild/config.mjs b/esbuild/config.mjs index 14acde3..9740e8b 100644 --- a/esbuild/config.mjs +++ b/esbuild/config.mjs @@ -14,7 +14,19 @@ const sharedOptions = { }, legalComments: 'inline', outdir: 'dist', - target: 'es2024' + target: 'es2024', + dropLabels: ['NODE'], + inject: ['./esbuild/inject/buffer.mjs'] +} + +/** +* @type {import('esbuild').BuildOptions} +*/ +export const libraryOptions = { + ...sharedOptions, + bundle: false, + format: 'esm', + entryPoints: ['./src/**/*.ts'] } /** @@ -24,11 +36,8 @@ export const browserOptions = { ...sharedOptions, format: 'esm', entryPoints: [ - { in: './src/main.ts', out: 'browser.min' }, - { in: './src/types.d.ts', out: 'types.d' } - ], - dropLabels: ['NODE'], - inject: ['./esbuild/inject/buffer.mjs'] + { in: './src/main.ts', out: 'browser.min' } + ] } /** @@ -39,11 +48,8 @@ export const iifeOptions = { format: 'iife', globalName: 'libnemo', entryPoints: [ - { in: './src/main.ts', out: 'global.min' }, - { in: './src/types.d.ts', out: 'types.d' } - ], - dropLabels: ['NODE'], - inject: ['./esbuild/inject/buffer.mjs'] + { in: './src/main.ts', out: 'global.min' } + ] } /** @@ -55,7 +61,7 @@ export const nodeOptions = { entryPoints: [ { in: './src/main.ts', out: 'nodejs.min' } ], - dropLabels: ['BROWSER'], external: ['node:worker_threads'], + dropLabels: ['BROWSER'], inject: ['./esbuild/inject/fake-indexeddb.mjs'] } diff --git a/esbuild/dev.mjs b/esbuild/dev.mjs index 1091bc1..3dccccb 100644 --- a/esbuild/dev.mjs +++ b/esbuild/dev.mjs @@ -2,8 +2,9 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { build } from 'esbuild' -import { browserOptions, iifeOptions, nodeOptions } from './config.mjs' +import { browserOptions, iifeOptions, libraryOptions, nodeOptions } from './config.mjs' await build(browserOptions) await build(iifeOptions) +await build(libraryOptions) await build(nodeOptions) diff --git a/esbuild/prod.mjs b/esbuild/prod.mjs index 769e302..44181e2 100644 --- a/esbuild/prod.mjs +++ b/esbuild/prod.mjs @@ -2,7 +2,7 @@ //! SPDX-License-Identifier: GPL-3.0-or-later import { build } from 'esbuild' -import { browserOptions, iifeOptions, nodeOptions } from './config.mjs' +import { browserOptions, iifeOptions, libraryOptions, nodeOptions } from './config.mjs' /** * @type {import('esbuild').BuildOptions} @@ -15,4 +15,5 @@ const prodOptions = { await build({ ...browserOptions, ...prodOptions }) await build({ ...iifeOptions, ...prodOptions }) +await build({ ...libraryOptions, ...prodOptions }) await build({ ...nodeOptions, ...prodOptions }) diff --git a/package-lock.json b/package-lock.json index ba6a61a..17da4b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "0.9.0", "license": "(GPL-3.0-or-later AND MIT AND ISC)", "dependencies": { + "@ledgerhq/hw-transport-web-ble": "^6.29.10", + "@ledgerhq/hw-transport-webhid": "^6.30.6", + "@ledgerhq/hw-transport-webusb": "^6.29.10", "nano-pow": "^5.1.6" }, "devDependencies": { @@ -24,26 +27,6 @@ "funding": { "type": "nano", "url": "nano:nano_1zosoqs47yt47bnfg7sdf46kj7asn58b7uzm9ek95jw7ccatq37898u1zoso" - }, - "peerDependencies": { - "@ledgerhq/hw-transport-web-ble": "^6.29.10", - "@ledgerhq/hw-transport-webhid": "^6.30.6", - "@ledgerhq/hw-transport-webusb": "^6.29.10", - "buffer": "^6.0.3" - }, - "peerDependenciesMeta": { - "@ledgerhq/hw-transport-web-ble": { - "optional": true - }, - "@ledgerhq/hw-transport-webhid": { - "optional": true - }, - "@ledgerhq/hw-transport-webusb": { - "optional": true - }, - "buffer": { - "optional": true - } } }, "node_modules/@babel/code-frame": { @@ -72,9 +55,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", "cpu": [ "ppc64" ], @@ -89,9 +72,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", "cpu": [ "arm" ], @@ -106,9 +89,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", "cpu": [ "arm64" ], @@ -123,9 +106,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", "cpu": [ "x64" ], @@ -140,9 +123,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", "cpu": [ "arm64" ], @@ -157,9 +140,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", "cpu": [ "x64" ], @@ -174,9 +157,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", "cpu": [ "arm64" ], @@ -191,9 +174,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", "cpu": [ "x64" ], @@ -208,9 +191,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", "cpu": [ "arm" ], @@ -225,9 +208,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", "cpu": [ "arm64" ], @@ -242,9 +225,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", "cpu": [ "ia32" ], @@ -259,9 +242,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", "cpu": [ "loong64" ], @@ -276,9 +259,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", "cpu": [ "mips64el" ], @@ -293,9 +276,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", "cpu": [ "ppc64" ], @@ -310,9 +293,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", "cpu": [ "riscv64" ], @@ -327,9 +310,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", "cpu": [ "s390x" ], @@ -344,9 +327,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", "cpu": [ "x64" ], @@ -361,9 +344,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", "cpu": [ "arm64" ], @@ -378,9 +361,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", "cpu": [ "x64" ], @@ -395,9 +378,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", "cpu": [ "arm64" ], @@ -412,9 +395,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", "cpu": [ "x64" ], @@ -429,9 +412,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", "cpu": [ "arm64" ], @@ -446,9 +429,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", "cpu": [ "x64" ], @@ -463,9 +446,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", "cpu": [ "arm64" ], @@ -480,9 +463,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", "cpu": [ "ia32" ], @@ -497,9 +480,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", "cpu": [ "x64" ], @@ -513,6 +496,79 @@ "node": ">=18" } }, + "node_modules/@ledgerhq/devices": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/@ledgerhq/devices/-/devices-8.5.1.tgz", + "integrity": "sha512-oW75YQQiP2muHveXTuwSAze6CBxJ7jOYILhFiJbsVzmgLPVqtdw4s0bJJlOBft4Aup67yNAjboFCIU7kTYQBFg==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/errors": "^6.25.0", + "@ledgerhq/logs": "^6.13.0", + "rxjs": "^7.8.1", + "semver": "^7.3.5" + } + }, + "node_modules/@ledgerhq/errors": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/errors/-/errors-6.25.0.tgz", + "integrity": "sha512-9cU0dgUyq3Adb1bHAjJnbwl+r+4WBjuPq0k+/DbBNpuYHwcz2xKtRIjLimUJyACjHti3iWwRt1sFcbQDDdI08w==", + "license": "Apache-2.0" + }, + "node_modules/@ledgerhq/hw-transport": { + "version": "6.31.10", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport/-/hw-transport-6.31.10.tgz", + "integrity": "sha512-ruNtkTPMO3rFCaSM+oPTOXXerzxWFZF43pAHVAHhsjiQGhLWzLSkMc7qBEpWIpZPubKRAbWSXR2zXBIJPNy8oQ==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.5.1", + "@ledgerhq/errors": "^6.25.0", + "@ledgerhq/logs": "^6.13.0", + "events": "^3.3.0" + } + }, + "node_modules/@ledgerhq/hw-transport-web-ble": { + "version": "6.29.10", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-web-ble/-/hw-transport-web-ble-6.29.10.tgz", + "integrity": "sha512-c4MrXSS4UfPsq0Qk7C1bLGXp7OnQtifDswaEQpvjoOQ57fArObh5rpYzUuBxNPHhwh/3zNcqIWNkR0oe4ugP5Q==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.5.1", + "@ledgerhq/errors": "^6.25.0", + "@ledgerhq/hw-transport": "^6.31.10", + "@ledgerhq/logs": "^6.13.0", + "rxjs": "^7.8.1" + } + }, + "node_modules/@ledgerhq/hw-transport-webhid": { + "version": "6.30.6", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-webhid/-/hw-transport-webhid-6.30.6.tgz", + "integrity": "sha512-AOzjFv5n1OTMfvEInAZRfLO11G5zfG8pB5zhmjK5d/mVQfFNcKsLZNAC2h4OZZMBtUy8UTT3n03z93e6k6n4pQ==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.5.1", + "@ledgerhq/errors": "^6.25.0", + "@ledgerhq/hw-transport": "^6.31.10", + "@ledgerhq/logs": "^6.13.0" + } + }, + "node_modules/@ledgerhq/hw-transport-webusb": { + "version": "6.29.10", + "resolved": "https://registry.npmjs.org/@ledgerhq/hw-transport-webusb/-/hw-transport-webusb-6.29.10.tgz", + "integrity": "sha512-3op7ipyZrM/Gg2qUBUM6tGKgYVh7k3hvXPE7z4i5ElAFHBdM5XMI4OD2cfl38XE0fij1dk/NCpKntOekIL7lmA==", + "license": "Apache-2.0", + "dependencies": { + "@ledgerhq/devices": "8.5.1", + "@ledgerhq/errors": "^6.25.0", + "@ledgerhq/hw-transport": "^6.31.10", + "@ledgerhq/logs": "^6.13.0" + } + }, + "node_modules/@ledgerhq/logs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/@ledgerhq/logs/-/logs-6.13.0.tgz", + "integrity": "sha512-4+qRW2Pc8V+btL0QEmdB2X+uyx0kOWMWE1/LWsq5sZy3Q5tpi4eItJS6mB0XL3wGW59RQ+8bchNQQ1OW/va8Og==", + "license": "Apache-2.0" + }, "node_modules/@puppeteer/browsers": { "version": "2.10.10", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.10.tgz", @@ -978,9 +1034,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -991,32 +1047,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" } }, "node_modules/escalade": { @@ -1085,6 +1141,15 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -1550,12 +1615,20 @@ "node": ">=4" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", - "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -1697,8 +1770,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/typed-query-selector": { "version": "2.12.0", diff --git a/package.json b/package.json index 6b0fd3f..e5f99fe 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "package.json.license" ], "types": "dist/types.d.ts", - "main": "dist/node.min.js", + "main": "dist/nodejs.min.js", "browser": { "./dist/browser.min.js": "./dist/browser.min.js", "node:worker_threads": false @@ -44,7 +44,7 @@ }, "scripts": { "clean": "rm -rf dist types && tsc", - "build": "npm run clean && node esbuild/dev.mjs", + "build": "npm run clean && node esbuild/dev.mjs && cp -r types dist", "build:prod": "npm run clean && node esbuild/prod.mjs", "prepublishOnly": "npm run test:prod", "reinstall": "rm -rf node_modules package-lock.json && npm cache clean --force && npm i", @@ -58,7 +58,10 @@ "#types": "./src/types.d.ts" }, "dependencies": { - "nano-pow": "^5.1.6" + "nano-pow": "^5.1.6", + "@ledgerhq/hw-transport-web-ble": "^6.29.10", + "@ledgerhq/hw-transport-webhid": "^6.30.6", + "@ledgerhq/hw-transport-webusb": "^6.29.10" }, "devDependencies": { "@types/node": "^24.5.0", @@ -70,33 +73,14 @@ "fake-indexeddb": "^6.2.2", "typescript": "^5.9.2" }, - "peerDependencies": { - "@ledgerhq/hw-transport-web-ble": "^6.29.10", - "@ledgerhq/hw-transport-webhid": "^6.30.6", - "@ledgerhq/hw-transport-webusb": "^6.29.10", - "buffer": "^6.0.3" - }, - "peerDependenciesMeta": { - "@ledgerhq/hw-transport-web-ble": { - "optional": true - }, - "@ledgerhq/hw-transport-webhid": { - "optional": true - }, - "@ledgerhq/hw-transport-webusb": { - "optional": true - }, - "buffer": { - "optional": true - } - }, "type": "module", "exports": { ".": { - "types": "./dist/types.d.ts", + "types": "./dist/types/main.d.ts", + "import": "./dist/main.js", "browser": "./dist/browser.min.js", "node": "./dist/nodejs.min.js", - "default": "./dist/browser.min.js" + "default": "./dist/global.min.js" } }, "unpkg": "dist/browser.min.js" diff --git a/src/lib/account/index.ts b/src/lib/account/index.ts index 7ecdc74..4717317 100644 --- a/src/lib/account/index.ts +++ b/src/lib/account/index.ts @@ -1,7 +1,6 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { KeyPair } from '#types' import { Block } from '../block' import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH } from '../constants' import { bytes, hex } from '../convert' @@ -11,6 +10,12 @@ import { Address } from './address' import { _refresh } from './refresh' import { _validate } from './validate' +type KeyPair = { + index?: number + privateKey?: string | Uint8Array + publicKey?: string | Uint8Array +} + /** * Represents a single Nano address and the associated public key. To include the * matching private key, it must be known at the time of object instantiation. diff --git a/src/lib/block.ts b/src/lib/block.ts index 21b4da0..0060907 100644 --- a/src/lib/block.ts +++ b/src/lib/block.ts @@ -7,7 +7,7 @@ import { BURN_PUBLIC_KEY, PREAMBLE, DIFFICULTY_RECEIVE, DIFFICULTY_SEND, UNITS } import { bytes, dec, hex } from './convert' import { Blake2b, NanoNaCl } from './crypto' import { Rpc } from './rpc' -import { convert } from './tools' +import { Tools } from './tools' import { Wallet } from './wallet' /** @@ -111,7 +111,7 @@ export class Block { throw new TypeError('Account frontier is unknown') } this.account = account - this.balance = convert(balance, 'raw', 'raw', 'bigint') + this.balance = Tools.convert(balance, 'raw', 'raw', 'bigint') this.previous = hex.toBytes(previous, 32) if (representative instanceof Account) { this.representative = representative @@ -311,7 +311,7 @@ export class Block { if (typeof amount !== 'bigint' && typeof amount !== 'number' && typeof amount !== 'string') { throw new TypeError('Invalid amount') } - this.balance += convert(amount, unit, 'raw', 'bigint') + this.balance += Tools.convert(amount, unit, 'raw', 'bigint') if (typeof sendBlock !== 'string' && !(sendBlock instanceof Block)) { throw new TypeError('Invalid send block') @@ -354,7 +354,7 @@ export class Block { if (typeof amount !== 'bigint' && typeof amount !== 'number' && typeof amount !== 'string') { throw new TypeError(`Invalid amount ${amount}`, { cause: typeof amount }) } - this.balance -= convert(amount, unit, 'raw', 'bigint') + this.balance -= Tools.convert(amount, unit, 'raw', 'bigint') if (this.balance < 0) { throw new RangeError('Insufficient funds', { cause: this.balance }) diff --git a/src/lib/crypto/wallet-aes-gcm.ts b/src/lib/crypto/wallet-aes-gcm.ts index b2e1013..33575d8 100644 --- a/src/lib/crypto/wallet-aes-gcm.ts +++ b/src/lib/crypto/wallet-aes-gcm.ts @@ -3,12 +3,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { NamedData } from "#types" - export class WalletAesGcm { static encoder: TextEncoder = new TextEncoder() - static decrypt (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise> { + static decrypt (type: string, key: CryptoKey, iv: ArrayBuffer, encrypted: ArrayBuffer): Promise<{ [key: string]: ArrayBuffer }> { const seedLength = type === 'BIP-44' ? 64 : 32 const additionalData = this.encoder.encode(type) return crypto.subtle @@ -21,7 +19,7 @@ export class WalletAesGcm { }) } - static encrypt (type: string, key: CryptoKey, seed: ArrayBuffer, mnemonic?: ArrayBuffer): Promise> { + static encrypt (type: string, key: CryptoKey, seed: ArrayBuffer, mnemonic?: ArrayBuffer): Promise<{ [key: string]: ArrayBuffer }> { if (type == null) { throw new Error('Wallet type missing') } diff --git a/src/lib/ledger.ts b/src/lib/ledger.ts index fa0de3d..1904907 100644 --- a/src/lib/ledger.ts +++ b/src/lib/ledger.ts @@ -1,13 +1,9 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -//@ts-expect-error import { default as TransportBLE } from '@ledgerhq/hw-transport-web-ble' -//@ts-expect-error import { default as TransportHID } from '@ledgerhq/hw-transport-webhid' -//@ts-expect-error import { default as TransportUSB } from '@ledgerhq/hw-transport-webusb' -import { LedgerStatus, LedgerAccountResponse, LedgerResponse, LedgerSignResponse, LedgerVersionResponse } from '#types' import { Account } from './account' import { Block } from './block' import { BIP44_COIN_NANO, BIP44_PURPOSE, HARDENED_OFFSET } from './constants' @@ -15,6 +11,27 @@ import { bytes, dec, hex } from './convert' import { Rpc } from './rpc' import { Wallet } from './wallet' +type LedgerStatus = 'UNSUPPORTED' | 'DISCONNECTED' | 'BUSY' | 'LOCKED' | 'CONNECTED' + +interface LedgerResponse { + status: string +} + +interface LedgerVersionResponse extends LedgerResponse { + name: string | null, + version: string | null +} + +interface LedgerAccountResponse extends LedgerResponse { + publicKey: string | null, + address: string | null +} + +interface LedgerSignResponse extends LedgerResponse { + signature: string | null, + hash?: string +} + /** * Ledger hardware wallet created by communicating with a Ledger device via ADPU * calls. This wallet does not feature any seed nor mnemonic phrase as all @@ -159,6 +176,14 @@ export class Ledger { } try { const version = await this.#version() + globalThis.navigator?.hid?.addEventListener('connect', console.log) + globalThis.navigator?.usb?.addEventListener('connect', console.log) + globalThis.navigator?.hid?.addEventListener('disconnect', console.log) + globalThis.navigator?.usb?.addEventListener('disconnect', console.log) + globalThis.navigator?.hid?.addEventListener('connect', this.#onConnectHid) + globalThis.navigator?.usb?.addEventListener('connect', this.#onConnectUsb) + globalThis.navigator?.hid?.addEventListener('disconnect', this.#onDisconnectHid) + globalThis.navigator?.usb?.addEventListener('disconnect', this.#onDisconnectUsb) if (version.status !== 'OK') { this.#status = 'DISCONNECTED' } else if (version.name === 'Nano') { @@ -186,17 +211,17 @@ export class Ledger { */ static disconnect (): void { setTimeout(async () => { - const hidDevices = await globalThis.navigator.hid.getDevices() + const hidDevices = await globalThis.navigator?.hid?.getDevices?.() ?? [] for (const device of hidDevices) { if (device.vendorId === this.UsbVendorId) { device.forget() } } - const bleDevices = await globalThis.navigator.bluetooth.getDevices() + const bleDevices = await globalThis.navigator?.bluetooth?.getDevices?.() ?? [] for (const device of bleDevices) { TransportBLE.disconnect(device.id) } - const usbDevices = await globalThis.navigator.usb.getDevices() + const usbDevices = await globalThis.navigator?.usb?.getDevices?.() ?? [] for (const device of usbDevices) { if (device.vendorId === this.UsbVendorId) { device.forget() @@ -383,6 +408,7 @@ export class Ledger { } static #onConnectHid = async (e: HIDConnectionEvent): Promise => { + console.log('onConnectHid') console.log(e) if (e.device?.vendorId === this.UsbVendorId) { console.log('Ledger connected via HID') @@ -395,6 +421,7 @@ export class Ledger { } static #onDisconnectHid = async (e: HIDConnectionEvent): Promise => { + console.log('onDisconnectHid') console.log(e) if (e.device?.vendorId === this.UsbVendorId) { console.log('Ledger disconnected via HID') @@ -408,6 +435,7 @@ export class Ledger { } static #onConnectUsb = async (e: USBConnectionEvent): Promise => { + console.log('onConnectUsb') console.log(e) if (e.device?.vendorId === this.UsbVendorId) { console.log('Ledger connected via USB') @@ -420,6 +448,7 @@ export class Ledger { } static #onDisconnectUsb = async (e: USBConnectionEvent): Promise => { + console.log('onDisconnectUsb') console.log(e) if (e.device?.vendorId === this.UsbVendorId) { console.log('Ledger disconnected via USB') diff --git a/src/lib/rolodex.ts b/src/lib/rolodex.ts index 93284c1..ec2b9ed 100644 --- a/src/lib/rolodex.ts +++ b/src/lib/rolodex.ts @@ -4,7 +4,7 @@ import { NamedData } from '#types' import { Account } from './account' import { Database } from './database' -import { verify } from './tools' +import { Tools } from './tools' /** * Represents a basic address book of Nano accounts. Multiple addresses can be @@ -184,7 +184,7 @@ export class Rolodex { const addresses = await this.getAddresses(name) for (const address of addresses) { const { publicKey } = Account.load(address) - const verified = await verify(publicKey, signature, ...data) + const verified = await Tools.verify(publicKey, signature, ...data) if (verified) { return true } diff --git a/src/lib/tools.ts b/src/lib/tools.ts index 6c862ab..7f33bf8 100644 --- a/src/lib/tools.ts +++ b/src/lib/tools.ts @@ -1,7 +1,6 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { SweepResult } from '#types' import { Account } from './account' import { Block } from './block' import { MAX_SUPPLY, UNITS } from './constants' @@ -10,206 +9,237 @@ import { Blake2b, NanoNaCl } from './crypto' import { Rpc } from './rpc' import { Wallet } from './wallet' -/** -* Converts a decimal amount of nano from one unit divider to another. -* -* @param {(bigint|number|string)} amount - Decimal amount to convert -* @param {string} inputUnit - Current denomination -* @param {string} outputUnit - Desired denomination -* @param {string} [format] - Data type of output -*/ -export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string): string -export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'bigint'): bigint -export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'number'): number -export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'string'): string -export function convert (amount: unknown, inputUnit: unknown, outputUnit: unknown, format?: unknown): bigint | number | string { - if (typeof amount !== 'bigint' && typeof amount !== 'number' && typeof amount !== 'string') { - throw new Error('Invalid amount', { cause: typeof amount }) - } - if (typeof amount === 'string' && !/^[0-9]+\.?[0-9]*$/.test(amount)) { - throw new Error('Invalid amount', { cause: amount }) - } - if (typeof inputUnit !== 'string') { - throw new TypeError('Invalid input unit', { cause: typeof inputUnit }) - } - (inputUnit as string) = inputUnit.toUpperCase() - if (typeof outputUnit !== 'string') { - throw new TypeError('Invalid output unit', { cause: typeof outputUnit }) - } - (outputUnit as string) = outputUnit.toUpperCase() - if (UNITS[inputUnit] == null) { - throw new Error(`Unknown denomination ${inputUnit}, expected one of the following: ${Object.keys(UNITS)}`) - } - if (UNITS[outputUnit] == null) { - throw new Error(`Unknown denomination ${outputUnit}, expected one of the following: ${Object.keys(UNITS)}`) - } - if (format !== undefined && format !== 'bigint' && format !== 'number' && format !== 'string') { - throw new Error('Invalid output format', { cause: format }) - } +type SweepResult = { + status: "success" | "error" + address: string + message: string +} - let [i, f] = typeof amount === 'string' - ? amount.split('.') - : amount.toString().split('.') - i = i.replace(/^0*/g, '') - f = f?.replace(/0*$/g, '') +export class Tools { + static #ledger: typeof import('./ledger').Ledger - const inUnit = UNITS[inputUnit] - const intShift = i.length + inUnit - if (f?.length > inUnit) { - throw new RangeError('Amount contains fractional raw') + static { + (async () => { + this.#ledger = (await import('./ledger')).Ledger + })() } - // convert to raw - let int = BigInt(i.padEnd(intShift, '0') || '0') - if (f != null) { - const fracShift = f?.length + inUnit - let frac = BigInt(f?.padEnd(fracShift, '0') || '0') - frac /= 10n ** BigInt(f.length) - int += frac - } - if (int > MAX_SUPPLY) { - throw new Error('Amount exceeds available supply') - } - if (int < 0n) { - throw new Error('Amount must be non-negative') - } + /** + * Converts a decimal amount of nano from one unit divider to another. + * + * @param {(bigint|number|string)} amount - Decimal amount to convert + * @param {string} inputUnit - Current denomination + * @param {string} outputUnit - Desired denomination + * @param {string} [format] - Data type of output + */ + static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string): string + static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'bigint'): bigint + static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'number'): number + static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'string'): string + static convert (amount: unknown, inputUnit: unknown, outputUnit: unknown, format?: unknown): bigint | number | string { + if (typeof amount !== 'bigint' && typeof amount !== 'number' && typeof amount !== 'string') { + throw new Error('Invalid amount', { cause: typeof amount }) + } + if (typeof amount === 'string' && !/^[0-9]+\.?[0-9]*$/.test(amount)) { + throw new Error('Invalid amount', { cause: amount }) + } + if (typeof inputUnit !== 'string') { + throw new TypeError('Invalid input unit', { cause: typeof inputUnit }) + } + (inputUnit as string) = inputUnit.toUpperCase() + if (typeof outputUnit !== 'string') { + throw new TypeError('Invalid output unit', { cause: typeof outputUnit }) + } + (outputUnit as string) = outputUnit.toUpperCase() + if (UNITS[inputUnit] == null) { + throw new Error(`Unknown denomination ${inputUnit}, expected one of the following: ${Object.keys(UNITS)}`) + } + if (UNITS[outputUnit] == null) { + throw new Error(`Unknown denomination ${outputUnit}, expected one of the following: ${Object.keys(UNITS)}`) + } + if (format !== undefined && format !== 'bigint' && format !== 'number' && format !== 'string') { + throw new Error('Invalid output format', { cause: format }) + } - // convert to desired denomination - const outUnit = UNITS[outputUnit] - i = int.toString().padStart(40, '0') - f = i.slice(40 - outUnit).replace(/0*$/g, '') - i = i.slice(0, 40 - outUnit).replace(/^0*/g, '') - const output = `${i === '' ? '0' : i}${f === '' ? '' : '.'}${f}` + let [i, f] = typeof amount === 'string' + ? amount.split('.') + : amount.toString().split('.') + i = i.replace(/^0*/g, '') + f = f?.replace(/0*$/g, '') - switch (format) { - case 'bigint': { - if (!f) return BigInt(output) - throw new RangeError('Output fractional amount truncated') + const inUnit = UNITS[inputUnit] + const intShift = i.length + inUnit + if (f?.length > inUnit) { + throw new RangeError('Amount contains fractional raw') } - case 'number': { - if (Number(i) <= Number.MAX_SAFE_INTEGER) return Number(output) - throw new RangeError('Output larger than Number.MAX_SAFE_INTEGER') - } - case 'string': - default: return output - } -} -function hash (data: string | string[], encoding?: 'hex'): Uint8Array -function hash (data: string | string[], encoding?: 'hex', format?: 'hex'): string | Uint8Array { - if (!Array.isArray(data)) data = [data] - const hash = new Blake2b(32) - if (encoding === 'hex') { - data.forEach(str => hash.update(hex.toBytes(str))) - } else { - const enc = new TextEncoder() - data.forEach(str => hash.update(enc.encode(str))) - } - return format === 'hex' - ? bytes.toHex(hash.digest()) - : hash.digest() -} + // convert to raw + let int = BigInt(i.padEnd(intShift, '0') || '0') + if (f != null) { + const fracShift = f?.length + inUnit + let frac = BigInt(f?.padEnd(fracShift, '0') || '0') + frac /= 10n ** BigInt(f.length) + int += frac + } + if (int > MAX_SUPPLY) { + throw new Error('Amount exceeds available supply') + } + if (int < 0n) { + throw new Error('Amount must be non-negative') + } -async function ledger (): Promise { - const { Ledger } = await import('./ledger') - return Ledger -} + // convert to desired denomination + const outUnit = UNITS[outputUnit] + i = int.toString().padStart(40, '0') + f = i.slice(40 - outUnit).replace(/0*$/g, '') + i = i.slice(0, 40 - outUnit).replace(/^0*/g, '') + const output = `${i === '' ? '0' : i}${f === '' ? '' : '.'}${f}` -/** -* Signs arbitrary strings with a private key using the Ed25519 signature scheme. -* The strings are first hashed to a 32-byte value using BLAKE2b. -* -* @param {string | Uint8Array} key - Hexadecimal-formatted private key to use for signing -* @param {...string} input - Data to be signed -* @returns {Promise} Hexadecimal-formatted signature -*/ -export async function sign (key: string | Uint8Array, ...input: string[]): Promise { - if (typeof key === 'string') key = hex.toBytes(key) - try { - const signature = await NanoNaCl.detached(hash(input), key) - return bytes.toHex(signature) - } catch (err) { - throw new Error(`Failed to sign message with private key`, { cause: err }) - } finally { - bytes.erase(key) + switch (format) { + case 'bigint': { + if (!f) return BigInt(output) + throw new RangeError('Output fractional amount truncated') + } + case 'number': { + if (Number(i) <= Number.MAX_SAFE_INTEGER) return Number(output) + throw new RangeError('Output larger than Number.MAX_SAFE_INTEGER') + } + case 'string': + default: return output + } } -} -/** -* Collects the funds from a specified range of accounts in a wallet and sends -* them all to a single recipient address. Hardware wallets are unsupported. -* -* @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks -* @param {(Wallet)} wallet - Wallet from which to sweep funds -* @param {string} recipient - Destination address for all swept funds -* @param {number} [from=0] - Starting account index to sweep -* @param {number} [to=from] - Ending account index to sweep -* @returns An array of results including both successes and failures - */ -export async function sweep ( - rpc: Rpc | string | URL, - wallet: Wallet, - recipient: string, - from: number = 0, - to: number = from -): Promise { - if (rpc == null || wallet == null || recipient == null) { - throw new ReferenceError('Missing required sweep arguments') + /** + * Hashes one or more arbitrary strings to a 32-byte value using BLAKE2b. + * + * @param {(string|string[])} data String(s) to hash + * @param {string} [encoding] Interprets input strings as hexadecimal values if 'hex' is passed, else UTF-8 + */ + static hash (data: string | string[], encoding?: 'hex'): Uint8Array + /** + * Hashes one or more arbitrary strings to a 32-byte value using BLAKE2b. + * + * @param {(string|string[])} data String(s) to hash + * @param {string} [encoding] Interprets input strings as hexadecimal values if 'hex' is passed, else UTF-8 + * @param {string} [format] Formats output as a hexadecimal value if 'hex' is passed, else Uint8Array + */ + static hash (data: string | string[], encoding?: 'hex', format?: 'hex'): string | Uint8Array { + if (!Array.isArray(data)) data = [data] + const hash = new Blake2b(32) + if (encoding === 'hex') { + data.forEach(str => hash.update(hex.toBytes(str))) + } else { + const enc = new TextEncoder() + data.forEach(str => hash.update(enc.encode(str))) + } + return format === 'hex' + ? bytes.toHex(hash.digest()) + : hash.digest() } - if (typeof rpc === 'string' || rpc instanceof URL) { - rpc = new Rpc(rpc) + + /** + * Provides low-level access to Ledger hardware wallets. + * + * @returns `Ledger` class with collection of static methods for managing the device + */ + static get Ledger (): typeof import('./ledger').Ledger { + return this.#ledger } - if (!(rpc instanceof Rpc)) { - throw new TypeError('RPC must be a valid node') + + /** + * Signs arbitrary strings with a private key using the Ed25519 signature scheme. + * The strings are first hashed to a 32-byte value using BLAKE2b. + * + * @param {string | Uint8Array} key - Hexadecimal-formatted private key to use for signing + * @param {...string} input - Data to be signed + * @returns {Promise} Hexadecimal-formatted signature + */ + static async sign (key: string | Uint8Array, ...input: string[]): Promise { + if (typeof key === 'string') key = hex.toBytes(key) + try { + const signature = await NanoNaCl.detached(this.hash(input), key) + return bytes.toHex(signature) + } catch (err) { + throw new Error(`Failed to sign message with private key`, { cause: err }) + } finally { + bytes.erase(key) + } } - const blockQueue: Promise[] = [] - const results: SweepResult[] = [] - const recipientAccount = Account.load(recipient) - const accounts = await wallet.refresh(rpc, from, to) - - for (const [index, account] of accounts) { - const blockRequest: Promise = new Promise(async (resolve) => { - let block - try { - if (index == null && account.index == null) { - throw new TypeError('Account index is required', { cause: account }) + /** + * Collects the funds from a specified range of accounts in a wallet and sends + * them all to a single recipient address. Hardware wallets are unsupported. + * + * @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks + * @param {(Wallet)} wallet - Wallet from which to sweep funds + * @param {string} recipient - Destination address for all swept funds + * @param {number} [from=0] - Starting account index to sweep + * @param {number} [to=from] - Ending account index to sweep + * @returns An array of results including both successes and failures + */ + static async sweep ( + rpc: Rpc | string | URL, + wallet: Wallet, + recipient: string, + from: number = 0, + to: number = from + ): Promise { + if (rpc == null || wallet == null || recipient == null) { + throw new ReferenceError('Missing required sweep arguments') + } + if (typeof rpc === 'string' || rpc instanceof URL) { + rpc = new Rpc(rpc) + } + if (!(rpc instanceof Rpc)) { + throw new TypeError('RPC must be a valid node') + } + + const blockQueue: Promise[] = [] + const results: SweepResult[] = [] + const recipientAccount = Account.load(recipient) + const accounts = await wallet.refresh(rpc, from, to) + + for (const [index, account] of accounts) { + const blockRequest: Promise = new Promise(async (resolve) => { + let block + try { + if (index == null && account.index == null) { + throw new TypeError('Account index is required', { cause: account }) + } + block = await new Block(account) + .send(recipientAccount, account.balance ?? 0n) + .sign(wallet, account.index ?? index) + await block.pow() + const hash = await block.process(rpc) + results.push({ status: 'success', address: block.account.address, message: hash }) + } catch (err: any) { + results.push({ status: 'error', address: account.address, message: err.message }) + } finally { + resolve() } - block = await new Block(account) - .send(recipientAccount, account.balance ?? 0n) - .sign(wallet, account.index ?? index) - await block.pow() - const hash = await block.process(rpc) - results.push({ status: 'success', address: block.account.address, message: hash }) - } catch (err: any) { - results.push({ status: 'error', address: account.address, message: err.message }) - } finally { - resolve() - } - }) - blockQueue.push(blockRequest) + }) + blockQueue.push(blockRequest) + } + await Promise.allSettled(blockQueue) + return results } - await Promise.allSettled(blockQueue) - return results -} -/** -* Verifies the signature of arbitrary strings using a public key. -* -* @param {string | Uint8Array} key - Hexadecimal-formatted public key to use for verification -* @param {string} signature - Hexadcimal-formatted signature -* @param {...string} input - Data to be verified -* @returns {Promise} True if the data was signed by the public key's matching private key -*/ -export async function verify (key: string | Uint8Array, signature: string, ...input: string[]): Promise { - if (typeof key === 'string') key = hex.toBytes(key) - try { - return await NanoNaCl.verify(hash(input), hex.toBytes(signature), key) - } catch (err) { - throw new Error('Failed to verify signature', { cause: err }) - } finally { - bytes.erase(key) + /** + * Verifies the signature of arbitrary strings using a public key. + * + * @param {string | Uint8Array} key - Hexadecimal-formatted public key to use for verification + * @param {string} signature - Hexadcimal-formatted signature + * @param {...string} input - Data to be verified + * @returns {Promise} True if the data was signed by the public key's matching private key + */ + static async verify (key: string | Uint8Array, signature: string, ...input: string[]): Promise { + if (typeof key === 'string') key = hex.toBytes(key) + try { + return await NanoNaCl.verify(this.hash(input), hex.toBytes(signature), key) + } catch (err) { + throw new Error('Failed to verify signature', { cause: err }) + } finally { + bytes.erase(key) + } } } - -export const Tools = { convert, hash, ledger, sign, sweep, verify } diff --git a/src/lib/vault/vault-worker.ts b/src/lib/vault/vault-worker.ts index 8d91f5a..2976eae 100644 --- a/src/lib/vault/vault-worker.ts +++ b/src/lib/vault/vault-worker.ts @@ -1,9 +1,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { NamedData, WalletType } from '#types' +import { NamedData } from '#types' import { BIP44_COIN_NANO } from '../constants' import { Bip39, Bip44, Blake2b, NanoNaCl, WalletAesGcm } from '../crypto' +import { WalletType } from '../wallet' import { Passkey } from './passkey' import { VaultTimer } from './vault-timer' diff --git a/src/lib/wallet/accounts.ts b/src/lib/wallet/accounts.ts index 556e7aa..d37f305 100644 --- a/src/lib/wallet/accounts.ts +++ b/src/lib/wallet/accounts.ts @@ -1,9 +1,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { KeyPair, WalletType } from '#types' import { Account } from '../account' +import { Ledger } from '../ledger' import { Vault } from '../vault' +import { WalletType } from '.' export async function _accounts (type: WalletType, accounts: Map, vault: Vault, index: number): Promise export async function _accounts (type: WalletType, accounts: Map, vault: Vault, from: number, to: number): Promise> @@ -27,7 +28,6 @@ export async function _accounts (type: WalletType, accounts: Map 0) { const publicAccounts = [] if (type === 'Ledger') { - const { Ledger } = await import('../ledger') for (const index of indexes) { const { status, publicKey } = await Ledger.account(index) if (status !== 'OK' || publicKey == null) { @@ -43,7 +43,7 @@ export async function _accounts (type: WalletType, accounts: Map 0) { publicAccounts.push(...Account.load(publicKeys)) } diff --git a/src/lib/wallet/config.ts b/src/lib/wallet/config.ts index 6166d0a..7d039f3 100644 --- a/src/lib/wallet/config.ts +++ b/src/lib/wallet/config.ts @@ -1,8 +1,9 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { WalletType } from '#types' +import { Ledger } from '../ledger' import { Vault } from '../vault' +import { WalletType } from '.' export async function _config (type: WalletType, vault: Vault, settings: { connection: 'hid' | 'ble' | 'usb' } | { timeout: number }): Promise export async function _config (type: WalletType, vault: Vault, settings: unknown): Promise { @@ -12,7 +13,6 @@ export async function _config (type: WalletType, vault: Vault, settings: unknown } const { connection, timeout } = settings as { [key: string]: unknown } if (type === 'Ledger') { - const { Ledger } = await import('../ledger') if (connection !== undefined && connection !== 'hid' && connection !== 'ble' && connection !== 'usb') { throw new Error('Ledger connection must be hid, ble, or usb', { cause: connection }) } diff --git a/src/lib/wallet/create.ts b/src/lib/wallet/create.ts index 691358d..20f44a9 100644 --- a/src/lib/wallet/create.ts +++ b/src/lib/wallet/create.ts @@ -4,6 +4,7 @@ import { NamedData } from '#types' import { utf8 } from '../convert' import { Database } from '../database' +import { Ledger } from '../ledger' import { Vault } from '../vault' import { Wallet } from '../wallet' import { _load } from './load' @@ -17,7 +18,6 @@ export async function _create (wallet: Wallet, vault: Vault, password: unknown, type: wallet.type } if (wallet.type === 'Ledger') { - const { Ledger } = await import('../ledger') try { if (Ledger.isUnsupported) { throw new Error('Browser is unsupported') diff --git a/src/lib/wallet/index.ts b/src/lib/wallet/index.ts index c73f2c1..8ad399e 100644 --- a/src/lib/wallet/index.ts +++ b/src/lib/wallet/index.ts @@ -1,11 +1,12 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { NamedData, WalletType } from '#types' +import { NamedData } from '#types' import { Account } from '../account' import { Block } from '../block' import { ADDRESS_GAP } from '../constants' import { bytes } from '../convert' +import { Ledger } from '../ledger' import { Rpc } from '../rpc' import { Vault } from '../vault' import { _accounts } from './accounts' @@ -24,6 +25,8 @@ import { _unopened } from './unopened' import { _update } from './update' import { _verify } from './verify' +export type WalletType = 'BIP-44' | 'BLAKE2b' | 'Ledger' + /** * Represents a wallet containing numerous Nano accounts derived from a single * source, the form of which can vary based on the type of wallet. Currently, @@ -31,7 +34,6 @@ import { _verify } from './verify' */ export class Wallet { static get DB_NAME (): 'Wallet' { return 'Wallet' } - static #ledger: typeof import('../ledger').Ledger /** * Retrieves all wallets with encrypted secrets and unencrypted metadata from @@ -61,7 +63,6 @@ export class Wallet { */ static async create (type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicSalt?: string): Promise static async create (type: WalletType, password?: string, mnemonicSalt?: string): Promise { - Wallet.#ledger ??= (await import('../ledger')).Ledger Wallet.#isInternal = true const self = new this(type) { ({ mnemonic: self.#mnemonic, seed: self.#seed } = await _create(self, self.#vault, password, mnemonicSalt)) } @@ -90,13 +91,9 @@ export class Wallet { */ static async load (type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicPhrase: string, mnemonicSalt?: string): Promise static async load (type: WalletType, password: string, secret: string, mnemonicSalt?: string): Promise { - Wallet.#ledger ??= (await import('../ledger')).Ledger Wallet.#isInternal = true const self = new this(type) await _load(self, self.#vault, password, secret, mnemonicSalt) - if (type === 'Ledger' && Wallet.#ledger === undefined) { - Wallet.#ledger = (await import('../ledger')).Ledger - } return self } @@ -115,7 +112,6 @@ export class Wallet { */ static async restore (): Promise static async restore (id?: string): Promise { - Wallet.#ledger ??= (await import('../ledger')).Ledger const backups = await _restore(id) const wallets = backups.map(backup => { Wallet.#isInternal = true @@ -149,7 +145,7 @@ export class Wallet { */ get isLocked (): boolean { return this.type === 'Ledger' - ? Wallet.#ledger.status !== 'CONNECTED' + ? Ledger.status !== 'CONNECTED' : this.#vault.isLocked } diff --git a/src/lib/wallet/load.ts b/src/lib/wallet/load.ts index 08cb559..4d6fb60 100644 --- a/src/lib/wallet/load.ts +++ b/src/lib/wallet/load.ts @@ -5,6 +5,7 @@ import { NamedData } from '#types' import { hex, utf8 } from '../convert' import { Bip39 } from '../crypto' import { Database } from '../database' +import { Ledger } from '../ledger' import { Vault } from '../vault' import { Wallet } from '../wallet' @@ -17,7 +18,6 @@ export async function _load (wallet: Wallet, vault: Vault, password: unknown, se } if (wallet.type === 'Ledger') { - const { Ledger } = await import('../ledger') if (Ledger.isUnsupported) { throw new Error('Failed to initialize Ledger wallet', { cause: 'Browser is unsupported' }) } diff --git a/src/lib/wallet/lock.ts b/src/lib/wallet/lock.ts index 14fc500..e647655 100644 --- a/src/lib/wallet/lock.ts +++ b/src/lib/wallet/lock.ts @@ -1,13 +1,13 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later +import { Ledger } from '../ledger' import { Vault } from '../vault' import { Wallet } from '../wallet' export async function _lock (wallet: Wallet, vault: Vault): Promise { try { if (wallet.type === 'Ledger') { - const { Ledger } = await import('../ledger') Ledger.disconnect() } else { await vault.request({ diff --git a/src/lib/wallet/refresh.ts b/src/lib/wallet/refresh.ts index 3166a81..6c7a6dd 100644 --- a/src/lib/wallet/refresh.ts +++ b/src/lib/wallet/refresh.ts @@ -1,12 +1,23 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { BlockInfo } from '#types' import { Account } from '../account' import { Block } from '../block' import { Rpc } from '../rpc' import { Wallet } from '../wallet' +export type BlockInfo = { + blocks: { + [hash: string]: { + [p: string]: string + } & { + contents: { + [p: string]: string + } + } + } +} + export async function _refresh (wallet: Wallet, rpc: Rpc | string | URL, from: number, to: number): Promise> export async function _refresh (wallet: Wallet, rpc: unknown, from: unknown, to: unknown): Promise> { try { diff --git a/src/lib/wallet/sign.ts b/src/lib/wallet/sign.ts index 4ff689e..eb1d097 100644 --- a/src/lib/wallet/sign.ts +++ b/src/lib/wallet/sign.ts @@ -4,6 +4,7 @@ import { Block } from '../block' import { bytes, hex, utf8 } from '../convert' import { Blake2b } from '../crypto' +import { Ledger } from '../ledger' import { Vault } from '../vault' import { Wallet } from '../wallet' @@ -44,7 +45,6 @@ export async function _signBlock (wallet: Wallet, vault: Vault, index: unknown, throw new TypeError('Invalid Block', { cause: block }) } if (wallet.type === 'Ledger') { - const { Ledger } = await import('../ledger') const account = await wallet.account(index) if (frontier instanceof Block) { try { diff --git a/src/lib/wallet/unlock.ts b/src/lib/wallet/unlock.ts index d021699..0c7cadd 100644 --- a/src/lib/wallet/unlock.ts +++ b/src/lib/wallet/unlock.ts @@ -3,6 +3,7 @@ import { NamedData } from '#types' import { utf8 } from '../convert' +import { Ledger } from '../ledger' import { Vault } from '../vault' import { Wallet } from '../wallet' import { _get } from './get' @@ -15,7 +16,6 @@ export async function _unlock (wallet: Wallet, vault: Vault, password: unknown): type: wallet.type } if (wallet.type === 'Ledger') { - const { Ledger } = await import('../ledger') const status = await Ledger.connect() if (await status !== 'CONNECTED') { throw new Error('Failed to unlock wallet', { cause: status }) diff --git a/src/lib/wallet/verify.ts b/src/lib/wallet/verify.ts index 47b439d..f364e7a 100644 --- a/src/lib/wallet/verify.ts +++ b/src/lib/wallet/verify.ts @@ -1,9 +1,10 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { NamedData, WalletType } from '#types' import { hex } from '../convert' +import { Ledger } from '../ledger' import { Vault } from '../vault' +import { WalletType } from '.' export async function _verify (type: WalletType, vault: Vault, secret: string): Promise export async function _verify (type: WalletType, vault: Vault, secret: unknown): Promise { @@ -12,10 +13,9 @@ export async function _verify (type: WalletType, vault: Vault, secret: unknown): throw new TypeError('Wallet secret must be a string', { cause: typeof secret }) } if (type === 'Ledger') { - const { Ledger } = await import('../ledger') return await Ledger.verify(secret) } else { - const data: NamedData = { + const data: { [key: string]: string | ArrayBuffer } = { action: 'verify' } if (/^(?:[A-F0-9]{64}){1,2}$/i.test(secret)) { diff --git a/src/main.ts b/src/main.ts index 432b4b9..75ee42e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { Account } from './lib/account' import { Block } from './lib/block' import { Blake2b } from './lib/crypto' +import { Ledger } from './lib/ledger' import { Rolodex } from './lib/rolodex' import { Rpc } from './lib/rpc' import { Tools } from './lib/tools' @@ -13,6 +14,7 @@ export { Account, Blake2b, Block, + Ledger, Rolodex, Rpc, Tools, diff --git a/src/types.d.ts b/src/types.d.ts index 31ea1f4..0daa045 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,958 +1,8 @@ //! SPDX-FileCopyrightText: 2025 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -/** -* Represents a single Nano address and the associated public key. To include the -* matching private key, it must be known at the time of object instantiation. -* The frontier, balance, and representative for the account can also be set or -* be fetched from the network. -*/ -export declare class Account { - #private - [key: string]: any - static get DB_NAME (): 'Account' - get address (): string - get index (): number | undefined - get publicKey (): string - get confirmed_balance (): bigint | undefined - get confirmed_height (): number | undefined - get confirmed_frontier (): string | undefined - get confirmed_frontier_block (): Block | undefined - get confirmed_receivable (): bigint | undefined - get confirmed_representative (): Account | undefined - get balance (): bigint | undefined - get block_count (): number | undefined - get frontier (): string | undefined - get frontier_block (): Block | undefined - get open_block (): string | undefined - get receivable (): bigint | undefined - get representative (): Account | undefined - get representative_block (): string | undefined - get weight (): bigint | undefined - set confirmed_balance (v: bigint | number | string) - set confirmed_height (v: number | undefined) - set confirmed_frontier (v: string | undefined) - set confirmed_frontier_block (v: Block | undefined) - set confirmed_receivable (v: bigint | number | string) - set confirmed_representative (v: unknown) - set balance (v: bigint | number | string) - set block_count (v: number | undefined) - set frontier (v: string | undefined) - set frontier_block (v: Block | undefined) - set open_block (v: string | undefined) - set receivable (v: bigint | number | string) - set representative (v: unknown) - set representative_block (v: string | undefined) - set weight (v: bigint | number | string) - private constructor () - /** - * Releases variable references to allow garbage collection. - */ - destroy (): Promise - /** - * Stringifies public properties into a JSON object so it can be further - * stringified by JSON.stringify() - */ - toJSON (): { - address: string - index: number | undefined - publicKey: string - confirmed_balance: string | undefined - confirmed_height: string | undefined - confirmed_frontier: string | undefined - confirmed_receivable: string | undefined - confirmed_representative: string | undefined - balance: string | undefined - block_count: number | undefined - frontier: string | undefined - open_block: string | undefined - receivable: string | undefined - representative: string | undefined - representative_block: string | undefined - weight: string | undefined - } - /** - * Instantiates an Account object from its Nano address. - * - * @param {string} address - Address of the account - * @returns {Account} A new Account object - */ - static load (address: string): Account - /** - * Instantiates Account objects from their Nano addresses. - * - * @param {string[]} addresses - Addresses of the accounts - * @returns {Account[]} Array of new Account objects - */ - static load (addresses: string[]): Account[] - /** - * Instantiates an Account object from its public key. It is unable to sign - * blocks or messages since it has no private key. - * - * @param {string | Uint8Array} publicKey - Public key of the account - * @returns {Account} A new Account object - */ - static load (publicKey: string | Uint8Array): Account - /** - * Instantiates Account objects from their public keys. They are unable to sign - * blocks or messages since they have no private key. - * - * @param {string | Uint8Array[]} publicKeys - Public keys of the accounts - * @returns {Account[]} Array of new Account objects - */ - static load (publicKeys: string | Uint8Array[]): Account[] - /** - * Instantiates an Account object from its public key. It is unable to sign - * blocks or messages since it has no private key. - * - * @param {KeyPair} keypair - Index and keys of the account - * @returns {Account} A new Account object - */ - static load (keypair: KeyPair): Account - /** - * Instantiates Account objects from their public keys. They are unable to sign - * blocks or messages since they have no private key. - * - * @param {KeyPair[]} keypairs - Indexes and keys of the accounts - * @returns {Account[]} Array of new Account objects - */ - static load (keypairs: KeyPair[]): Account[] - /** - * Instantiates an Account object from its private key which is used to derive - * a public key and then discarded. - * - * @param {KeyPair} keypair - Index and key of the account - * @param {string} type - Indicates a private key - * @returns {Promise} Promise for a new Account object - */ - static load (keypair: KeyPair, type: 'private'): Promise - /** - * Instantiates Account objects from their private keys which are used to - * derive public keys and then discarded. - * - * @param {KeyPair[]} keypairs - Indexes and keys of the accounts - * @param {string} type - Indicates private keys - * @returns {Promise} Promise for array of new Account objects - */ - static load (keypairs: KeyPair[], type: 'private'): Promise - /** - * Refreshes the account from its current state on the network. - * - * A successful response sets the balance, frontier, and representative - * properties. - * - * @param {Rpc|string|URL} rpc - RPC node information required to call `account_info` - */ - refresh (rpc: Rpc | string | URL): Promise - /** - * Validates a Nano address with 'nano' and 'xrb' prefixes. - * Derived from https://github.com/alecrios/nano-address-validator - * - * @param {string} address - Nano address to validate - * @throws Error if address is undefined, not a string, or an invalid format - */ - static validate (address: string): asserts address is string -} - -/** -* Represents a mnemonic phrase that identifies a wallet as defined by BIP-39. -*/ -export declare class Bip39 { - #private - encoder: TextEncoder - /** - * SHA-256 hash of entropy that is appended to the entropy and subsequently - * used to generate the mnemonic phrase. - * - * @param {Uint8Array} entropy - Cryptographically strong pseudorandom data of length N bits - * @returns {Promise} First N/32 bits of the hash as a bigint - */ - static checksum (entropy: Uint8Array): Promise - /** - * Derives a mnemonic phrase from source of entropy or seed. - * - * The entropy must be between 16-32 bytes (32-64 characters) to stay within - * the limit of 128-256 bits defined in BIP-39. Typically, wallets use the - * maximum entropy allowed. - * - * @param {(ArrayBuffer|Uint8Array)} entropy - Cryptographically secure random value - * @returns {Promise} Mnemonic phrase created using the BIP-39 wordlist - */ - static fromEntropy (entropy: ArrayBuffer | Uint8Array): Promise - /** - * Imports and validates an existing mnemonic phrase. - * - * The phrase must be valid according to the BIP-39 specification. Typically, - * wallets use the maximum of 24 words. - * - * @param {string} phrase - String of 12, 15, 18, 21, or 24 words - * @returns {Promise} Mnemonic phrase validated using the BIP-39 wordlist - */ - static fromPhrase (phrase: string): Promise - /** - * Validates a mnemonic phrase meets the BIP-39 specification. - * - * @param {string} mnemonic - Mnemonic phrase to validate - * @returns {Promise} True if the mnemonic phrase is valid - */ - static validate (mnemonic: string): Promise - private constructor () - /** - * BIP-39 mnemonic phrase, normlized using Normalization Form Compatibility - * Decomposition (NFKD). - */ - get phrase (): string | undefined - /** - * Erases seed bytes and releases variable references. - */ - destroy (): void - /** - * Converts the mnemonic phrase to a BIP-39 seed. - * - * A passphrase string can be specified. If the passphrase is undefined, null, - * or not a string, the empty string ("") is used instead. - * - * @param {string} [passphrase=''] - Used as the PBKDF2 salt. Default: "" - * @returns {Promise>} Promise for seed as bytes - */ - toBip39Seed (passphrase: string): Promise> - /** - * Converts the mnemonic phrase to a BIP-39 seed. - * - * A passphrase string can be specified. If the passphrase is undefined, null, - * or not a string, the empty string ("") is used instead. - * - * @param {string} [passphrase=''] - Used as the PBKDF2 salt. Default: "" - * @returns {Promise} Promise for seed as hexadecimal string - */ - toBip39Seed (passphrase: string, format: 'hex'): Promise - /** - * Converts the mnemonic phrase to a BLAKE2b seed. - * - * @returns {Uint8Array} Seed as bytes - */ - toBlake2bSeed (): Uint8Array - /** - * Converts the mnemonic phrase to a BLAKE2b seed. - * - * @param {string} format - * @returns {string} Seed as hexadecimal string - */ - toBlake2bSeed (format: 'hex'): string - /** - * https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt - */ - static wordlist: readonly string[] -} - -/** -* Implementation derived from blake2b@2.1.4. Copyright 2017 Emil Bay -* (https://github.com/emilbayes/blake2b). See LICENSES/ISC.txt -* -* Modified to use BigUint64 values, eliminate dependencies, port to TypeScript, -* and embed in web workers. -* -* Original source commit: https://github.com/emilbayes/blake2b/blob/1f63e02e3f226642959506cdaa67c8819ff145cd/index.js -*/ -export declare class Blake2b { - #private - /** - * Derives account private keys from a wallet seed using the BLAKE2b hashing - * algorithm. - * - * Separately, account public keys are derived from the private key using the - * Ed25519 key algorithm, and account addresses are derived from the public key - * as described in the Nano documentation. - * https://docs.nano.org/integration-guides/the-basics/ - * - * @param {ArrayBuffer} seed - 32-byte secret seed of the wallet - * @param {number} index - 4-byte index of account to derive - * @returns {ArrayBuffer} Private key for the account - */ - static ckd (seed: ArrayBuffer, index: number): Promise - static get OUTBYTES_MIN (): 1 - static get OUTBYTES_MAX (): 64 - static get KEYBYTES_MIN (): 1 - static get KEYBYTES_MAX (): 64 - static get SALTBYTES (): 16 - static get PERSONALBYTES (): 16 - static get IV (): bigint[] - static get SIGMA (): number[][] - /** - * Creates a BLAKE2b hashing context. - * - * @param {number} length - Output length between 1-64 bytes - * @param {Uint8Array} [key] - (_optional_) Used for keyed hashing (MAC and PRF) - * @param {Uint8Array} [salt] - (_optional_) Used to simplify randomized hashing for digital signatures - * @param {Uint8Array} [personal] - (_optional_) Arbitrary user-specified value - */ - constructor (length: number, key?: Uint8Array, salt?: Uint8Array, personal?: Uint8Array) - update (input: ArrayBuffer | Uint8Array): Blake2b - digest (): Uint8Array -} - -/** -* Represents a block as defined by the Nano cryptocurrency protocol. -*/ -export declare class Block { - #private - [key: string]: bigint | string | Account | Function | Uint8Array | 'send' | 'receive' | 'change' | undefined - subtype?: 'send' | 'receive' | 'change' - account: Account - balance: bigint - previous: Uint8Array - representative?: Account - link?: Uint8Array - signature?: string - work?: string - /** - * Initialize a block with the current state of an account so that it can - * subsequently be configured as a change, receive, or send transaction. - * - * All parameters are eventually required in order to initialize the block, but - * but if `account` is an Account class object, its properties can be used for - * the other parameters instead of passing them into the constructor. - * - * @param {(string|Account)} account - Target of the transaction; can include `balance`, `frontier`, `representative` - * @param {(bigint|number|string)} [balance] - Current balance of the target account in raw - * @param {string} [previous] - Current frontier block hash of the target account - * @param {(string|Account)} [representative] - Current representative of the target account - */ - constructor (account: string | Account, balance?: bigint | number | string, previous?: string, representative?: string | Account) - /** - * Calculates the block hash using Blake2b. - * - * @returns {string} Hexadecimal representation of 32-byte hash of block data - */ - get hash (): string - /** - * Converts the block to JSON format as expected by the `process` RPC. - * - * @returns {string} JSON representation of the block - */ - toJSON (): { - [key: string]: string - } - /** - * Set the subtype, link, and target account to configure this as a change - * representative block. - * - * @param {(string|Account)} account - Account to choose as representative, or its address or public key - * @returns {Block} This block with link, representative, and subtype configured - */ - change (representative: string | Account): Block - /** - * Calculates proof-of-work using a pool of Web Workers. - * - * A successful response sets the `work` property. - */ - pow (work?: string): Promise - /** - * Sends the block to a node for processing on the network. - * - * The block must already be signed (see `sign()` for more information). - * The block must also have a `work` value. - * - * @param {Rpc|string|URL} rpc - RPC node information required to call `process` - * @returns {Promise} Hash of the processed block - */ - process (rpc: Rpc): Promise - /** - * Set the amount of nano that this block will receive from a corresponding - * send block. - * - * @param {(string|Block)} sendBlock - Corresponding send block or its hash - * @param {(bigint|number|string)} amount - Amount to be received from sender - * @param {string} [unit] - Unit of measure for amount (e.g. 'NANO' = 10³⁰ RAW). Default: "RAW" - * @returns {Block} This block with balance, link, and subtype configured - */ - receive (sendBlock: string | Block, amount: bigint | number | string, unit?: string): Block - /** - * Set the amount of nano that this block will send to a recipient account. - * - * @param {(string|Account)} account - Account to target or its address or public key - * @param {(bigint|number|string)} amount - Amount to send to recipient - * @param {string} [unit] - Unit of measure for amount (e.g. 'NANO' = 10³⁰ RAW). Default: "RAW" - * @returns {Block} This block with balance, link, and subtype configured - */ - send (account: string | Account, amount: bigint | number | string, unit?: string): Block - /** - * Sets the `signature` property of the block to a precalculated value. - * - * @param {string} signature - 64-byte hexadecimal signature - * @returns Block with `signature` value set - */ - sign (signature: string): Block - /** - * Signs the block using a private key. If successful, the result is stored in - * the `signature` property of the block. - * - * @param {string} [key] - 32-byte hexadecimal private key to use for signing - * @returns Block with `signature` value set - */ - sign (key: string): Promise - /** - * Signs the block using a Wallet. If successful, the result is stored in - * the `signature` property of the block. The wallet must be unlocked prior to - * signing. - * - * @param {Wallet} wallet - Wallet to use for signing - * @param {number} index - Account in wallet to use for signing - * @returns Block with `signature` value set - */ - sign (wallet: Wallet, index: number): Promise - /** - * Signs the block using a Ledger hardware wallet. If that fails, an error is - * thrown with the status code from the device. If successful, the result is - * stored in the `signature` property of the block. The wallet must be unlocked - * prior to signing. - * - * @param {number} index - Account index between 0x0 and 0x7fffffff - * @param {object} [frontier] - JSON of frontier block for offline signing - * @returns Block with `signature` value set - */ - sign (wallet: Wallet, index: number, frontier?: Block): Promise - /** - * Verifies the signature of the block. If a key is not provided, the public - * key of the block's account will be used if it exists. - * - * @param {string} [key] - Hexadecimal-formatted public key to use for verification - * @returns {boolean} True if block was signed by the matching private key - */ - verify (key?: string): Promise -} - -export type BlockInfo = { - blocks: { - [hash: string]: { - [p: string]: string - } & { - contents: { - [p: string]: string - } - } - } -} - -export type Data = boolean | number | number[] | string | string[] | Account | ArrayBuffer | CryptoKey | { [key: string]: Data } +export type Data = boolean | number | number[] | string | string[] | ArrayBuffer | CryptoKey | { [key: string]: Data } export type NamedData = { [key: string]: T } - -export type KeyPair = { - index?: number - privateKey?: string | Uint8Array - publicKey?: string | Uint8Array -} - -export type LedgerStatus = 'UNSUPPORTED' | 'DISCONNECTED' | 'BUSY' | 'LOCKED' | 'CONNECTED' - -interface LedgerResponse { - status: string -} - -interface LedgerVersionResponse extends LedgerResponse { - name: string | null, - version: string | null -} - -interface LedgerAccountResponse extends LedgerResponse { - publicKey: string | null, - address: string | null -} - -interface LedgerSignResponse extends LedgerResponse { - signature: string | null, - hash?: string -} - -/** -* Ledger hardware wallet created by communicating with a Ledger device via ADPU -* calls. This wallet does not feature any seed nor mnemonic phrase as all -* private keys are held in the secure chip of the device. As such, the user -* is responsible for using Ledger technology to back up these pieces of data. -* -* https://github.com/roosmaa/ledger-app-nano/blob/master/doc/nano.md -*/ -export declare class Ledger { - #private - static get UsbVendorId (): 0x2c97 - /** - * Check which transport protocols are supported by the browser and return the - * transport type according to the following priorities: HID, Bluetooth, USB. - */ - static get isUnsupported (): boolean - /** - * Status of the Ledger device connection. - * - * DISCONNECTED | BUSY | LOCKED | CONNECTED - */ - static get status (): LedgerStatus - /** - * Request an account at a specific BIP-44 index. - * - * @returns Response object containing command status, public key, and address - */ - static account (index?: number, show?: boolean): Promise - /** - * Check if the Nano app is currently open and set device status accordingly. - * - * @param {string} [api] Transport interface to use - * @returns Device status as follows: - * - DISCONNECTED: Failed to communicate properly with the app - * - BUSY: Nano app is not currently open - * - LOCKED: Nano app is open but the device locked after a timeout - * - CONNECTED: Nano app is open and listening - */ - static connect (api?: 'hid' | 'ble' | 'usb'): Promise - /** - * Clears Ledger connections from all device interfaces. - */ - static disconnect (): void - /** - * Sign a block with the Ledger device. - * - * @param {number} index - Account number - * @param {Block} block - Block data to sign - * @param {Block} [frontier] - Previous block data to cache in the device - */ - static sign (index: number, block: Block, frontier?: Block): Promise - /** - * Update cache from raw block data. Suitable for offline use. - * - * @param {number} index - Account number - * @param {object} block - JSON-formatted block data - */ - static updateCache (index: number, block: Block): Promise - /** - * Update cache from a block hash by calling out to a node. Suitable for online - * use only. - * - * @param {number} index - Account number - * @param {string} hash - Hexadecimal block hash - * @param {Rpc} rpc - Rpc class object with a node URL - */ - static updateCache (index: number, hash: string, rpc: Rpc): Promise - /** - * Checks whether a given seed matches the wallet seed. The wallet must be - * unlocked prior to verification. - * - * @param {string} seed - Hexadecimal seed to be matched against the wallet data - * @returns True if input matches wallet seed - */ - static verify (seed: string): Promise - /** - * Checks whether a given mnemonic phrase matches the wallet mnemonic. If a - * personal salt was used when generating the mnemonic, it cannot be verified. - * The wallet must be unlocked prior to verification. - * - * @param {string} mnemonic - Phrase to be matched against the wallet data - * @returns True if input matches wallet mnemonic - */ - static verify (mnemonic: string): Promise -} - -/** -* Represents a basic address book of Nano accounts. Multiple addresses can be -* saved under one nickname. -*/ -export declare class Rolodex { - static get DB_NAME (): 'Rolodex' - /** - * Adds an address to the rolodex under a specific nickname. - * - * If the name exists, add the address as a new association to that name. If - * the account exists under a different name, update the name. If no matches - * are found at all, add a new entry. - * - * @param {string} name - Contact alias for the address - * @param {string} address - Nano account address - * @returns {Promise} Promise for true if name and address are added to the rolodex, else false - */ - static add (name: string, address: string): Promise - /** - * Removes a Nano address from its related contact in the rolodex. - * - * @param {string} address - Nano account address - * @returns {Promise} Promise for true if address successfully removed, else false - */ - static deleteAddress (address: string): Promise - /** - * Removes a contact and its related Nano addresses from the rolodex. - * - * @param {string} name - Contact name to delete - * @returns {Promise} Promise for true if name and related addresses successfully removed, else false - */ - static deleteName (name: string): Promise - /** - * Gets all Nano account addresses associated with a name from the rolodex. - * - * @param {string} name - Alias to look up - * @returns {Promise} Promise for a list of Nano addresses associated with the name - */ - static getAddresses (name: string): Promise - /** - * Gets all names stored in the rolodex. - * - * @returns {Promise} Promise for a list of all names stored in the rolodex - */ - static getAllNames (): Promise - /** - * Gets the name associated with a specific Nano address from the rolodex. - * - * @param {string} address - Nano account address - * @returns {Promise} Promise for the name associated with the address, or null if not found - */ - static getName (address: string): Promise - /** - * Verifies whether the public key of any Nano address saved under a specific - * name in the rolodex was used to sign a specific set of data. - * - * @param {string} name - Alias to look up - * @param {string} signature - Signature to use for verification - * @param {...string} data - Signed data to verify - * @returns {Promise} True if the signature was used to sign the data, else false - */ - static verify (name: string, signature: string, ...data: string[]): Promise -} - -/** -* Represents a Nano network node. It primarily consists of a URL which will -* accept RPC calls, and an optional API key header construction can be passed if -* required by the node. Once instantiated, the Rpc object can be used to call -* any action supported by the Nano protocol. The URL protocol must be HTTPS; any -* other value will be changed automatically. -*/ -export declare class Rpc { - #private - constructor (url: string | URL, apiKeyName?: string) - /** - * Sends a nano RPC call to a node endpoint. - * - * @param {string} action - Nano protocol RPC call to execute - * @param {object} [data] - JSON to send to the node as defined by the action - * @returns {Promise} JSON-formatted RPC results from the node - */ - call (action: string, data?: { - [key: string]: unknown - }): Promise -} - -type SweepResult = { - status: "success" | "error" - address: string - message: string -} - -/** -* Converts a decimal amount of nano from one unit divider to another. -* -* @param {(bigint|number|string)} amount - Decimal amount to convert -* @param {string} inputUnit - Current denomination -* @param {string} outputUnit - Desired denomination -* @param {string} [format] - Data type of output -*/ -export declare function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string): string -export declare function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'bigint'): bigint -export declare function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'number'): number -export declare function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'string'): string -declare function hash (data: string | string[], encoding?: 'hex'): Uint8Array -/** -* Signs arbitrary strings with a private key using the Ed25519 signature scheme. -* The strings are first hashed to a 32-byte value using BLAKE2b. -* -* @param {string | Uint8Array} key - Hexadecimal-formatted private key to use for signing -* @param {...string} input - Data to be signed -* @returns {Promise} Hexadecimal-formatted signature -*/ -export declare function sign (key: string | Uint8Array, ...input: string[]): Promise -/** -* Collects the funds from a specified range of accounts in a wallet and sends -* them all to a single recipient address. Hardware wallets are unsupported. -* -* @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks -* @param {(Wallet)} wallet - Wallet from which to sweep funds -* @param {string} recipient - Destination address for all swept funds -* @param {number} [from=0] - Starting account index to sweep -* @param {number} [to=from] - Ending account index to sweep -* @returns An array of results including both successes and failures - */ -export declare function sweep (rpc: Rpc | string | URL, wallet: Wallet, recipient: string, from?: number, to?: number): Promise -/** -* Verifies the signature of arbitrary strings using a public key. -* -* @param {string | Uint8Array} key - Hexadecimal-formatted public key to use for verification -* @param {string} signature - Hexadcimal-formatted signature -* @param {...string} input - Data to be verified -* @returns {Promise} True if the data was signed by the public key's matching private key -*/ -export declare function verify (key: string | Uint8Array, signature: string, ...input: string[]): Promise -export declare const Tools: { - convert: typeof convert - hash: typeof hash - sign: typeof sign - sweep: typeof sweep - verify: typeof verify -} - -export type WalletType = 'BIP-44' | 'BLAKE2b' | 'Ledger' - -/** -* Represents a wallet containing numerous Nano accounts derived from a single -* source, the form of which can vary based on the type of wallet. Currently, -* three types of wallets are supported: BIP-44, BLAKE2b, and Ledger. -*/ -export declare class Wallet { - #private - static get DB_NAME (): 'Wallet' - /** - * Retrieves all wallets with encrypted secrets and unencrypted metadata from - * the database. - * - * @returns id, type, iv, salt, encrypted - */ - static backup (): Promise - /** - * Creates a new hardware wallet manager. - * - * @param {string} type - Wallet manufacturer - * @returns {Wallet} A newly instantiated Wallet - */ - static create (type: 'Ledger'): Promise - /** - * Creates a new HD wallet by using an entropy value generated using a - * cryptographically strong pseudorandom number generator. - * - * @param {string} type - Algorithm used to generate wallet and child accounts - * @param {string} password - Encrypts the wallet to lock and unlock it - * @param {string} [salt=''] - Used when generating the final seed - * @returns {Wallet} A newly instantiated Wallet - */ - static create (type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicSalt?: string): Promise - /** - * Imports an existing HD wallet by using an entropy value generated using a - * cryptographically strong pseudorandom number generator.NamedD - * - * @param {string} type - Algorithm used to generate wallet and child accounts - * @param {string} password - Encrypts the wallet to lock and unlock it. Discard as soon as possible after loading the wallet. - * @param {string} seed - Used to derive child accounts - * @returns Wallet in a locked state - */ - static load (type: 'BIP-44' | 'BLAKE2b', password: string, seed: string): Promise - /** - * Imports an existing HD wallet by using an entropy value generated using a - * cryptographically strong pseudorandom number generator. - * - * @param {string} type - Algorithm used to generate wallet and child accounts - * @param {string} password - Encrypts the wallet to lock and unlock it. Discard as soon as possible after loading the wallet. - * @param {string} mnemonicPhrase - Used to derive the wallet seed - * @param {string} [mnemonicSalt] - Used to alter the seed derived from the mnemonic phrase - * @returns Wallet in a locked state - */ - static load (type: 'BIP-44' | 'BLAKE2b', password: string, mnemonicPhrase: string, mnemonicSalt?: string): Promise - /** - * Instantiates a Wallet from an existing record in the database using its UUID. - * - * @param {string} id - Generated when the wallet was created or imported - * @returns {Promise} Restored locked Wallet - */ - static restore (id: string): Promise - /** - * Instantiates Wallet objects from records in the database. - * - * @param {string} id - Generated when the wallet was created or imported - * @returns {Promise} Restored locked Wallets - */ - static restore (): Promise - constructor (type: WalletType, id?: string) - /** - * @returns UUID of the wallet. - */ - get id (): string - /** - * @returns True if the wallet is locked, else false - */ - get isLocked (): boolean - /** - * Algorithm or device used to create wallet and derive accounts. - */ - get type (): WalletType - /** - * Set when calling `create()`. Self-destructs after the first read. - * @returns Mnemonic phrase used to derive the wallet seed. - */ - get mnemonic (): string | undefined - /** - * Set when calling `create()`. Self-destructs after the first read. - * @returns Seed used to derive accounts. - */ - get seed (): string | undefined - /** - * Retrieves an account from a wallet using its child key derivation function. - * Defaults to the first account at index 0. - * - * The returned object will have keys corresponding with the requested range - * of account indexes. The value of each key will be the Account derived for - * that index in the wallet. - * - * ``` - * const account = await wallet.account(1) - * // outputs the second account of the wallet - * console.log(account) - * // { address: <...>, publicKey: <...>, index: 1, } - * ``` - * - * @param {number} index - Wallet index of account. Default: 0 - * @returns Promise for the Account at the specified index - */ - account (index?: number): Promise - /** - * Retrieves accounts from a wallet using its child key derivation function. - * Defaults to the first account at index 0. - * - * The returned object will have keys corresponding with the requested range - * of account indexes. The value of each key will be the Account derived for - * that index in the wallet. - * - * ``` - * const accounts = await wallet.accounts(0, 1)) - * // outputs the first and second account of the wallet - * console.log(accounts) - * // { - * // 0: { - * // address: <...>, - * // publicKey: <...>, - * // index: 0, - * // - * // }, - * // 1: { - * // address: <...>, - * // publicKey: <...>, - * // index: 1, - * // - * // } - * // } - * // individual accounts can be referenced using array index notation - * console.log(accounts[1]) - * // { address: <...>, publicKey: <...>, index: 1, } - * ``` - * - * If `from` is greater than `to`, their values will be swapped. - * @param {number} [from=0] - Start index of accounts. Default: 0 - * @param {number} [to=from] - End index of accounts. Default: `from` - * @returns {Promise>} Promise for a list of Accounts at the specified indexes - */ - accounts (from?: number, to?: number): Promise> - /** - * Configures Ledger connection settings. - * @param {string} connection - Transport interface to use - */ - config (settings: { - connection: 'hid' | 'ble' | 'usb' - }): Promise - /** - * Configures vault worker settings. - * @param {number} timeout - Measured in seconds of inactivity before wallet automatically locks - */ - config (settings: { - timeout: number - }): Promise - /** - * Removes encrypted secrets in storage, releases variable references to - * allow garbage collection, and terminates vault worker. - */ - destroy (): Promise - /** - * For BIP-44 and BLAKE2b wallets, clears the seed and mnemonic from the vault. - * - * For Ledger hardware wallets, revokes permission granted by the user to - * access the Ledger device. The 'quit app' ADPU command is uncooperative, so - * this is currently the only way to ensure the connection is severed. - * `setTimeout` is used to expire any lingering transient user activation. - */ - lock (): void - /** - * Refreshes wallet account balances, frontiers, and representatives from the - * current state on the network. - * - * A successful response will set these properties on each account. - * - * @param {(Rpc|string|URL)} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks - * @returns {Promise>} Promise for accounts with updated balances, frontiers, and representatives - */ - refresh (rpc: Rpc | string | URL, from?: number, to?: number): Promise> - /** - * Signs arbitrary strings using the private key of the account at the wallet - * index specified and returns a detached signature as a hexadecimal string. - * For compatibility with other Nano tools, the data is first hashed to a - * 32-byte value using BLAKE2b. The wallet must be unlocked prior to signing. - * - * @param {number} index - Account to use for signing - * @param {(string|string[])} data - Arbitrary data to be hashed and signed - */ - sign (index: number, data: string | string[]): Promise - /** - * Signs a block using the private key of the account at the wallet index - * specified. The signature is appended to the signature field of the block - * before being returned. The wallet must be unlocked prior to signing. - * - * @param {number} index - Account to use for signing - * @param {Block} block - Block data to be hashed and signed - */ - sign (index: number, block: Block): Promise - /** - * Signs a block using a Ledger hardware wallet at the wallet index specified. - * The signature is appended to the signature field of the block before being - * returned. The wallet must be unlocked prior to signing. - * - * @param {number} index - Account to use for signing - * @param {Block} block - Block data to be hashed and signed - * @param {Block} [frontier] - Previous block data to be cached by Ledger wallet - */ - sign (index: number, block: Block, frontier?: Block): Promise - /** - * Attempts to connect to the Ledger device. - */ - unlock (): Promise - /** - * Unlocks the wallet using the same password as used prior to lock it. - * - * @param {string} password Used previously to lock the wallet - */ - unlock (password: string): Promise - /** - * Fetches the lowest-indexed unopened account from a wallet in sequential - * order. An account is unopened if it has no frontier block. - * - * @param {Rpc|string|URL} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks - * @param {number} batchSize - Number of accounts to fetch and check per RPC callout - * @param {number} from - Account index from which to start the search - * @returns {Promise} The lowest-indexed unopened account belonging to the wallet - */ - unopened (rpc: Rpc, batchSize?: number, from?: number): Promise - /** - * Updates the password used to encrypt the wallet. The wallet must be unlocked - * prior to update. - * - * @param {string} password Used to re-encrypt the wallet - */ - update (password: string): Promise - /** - * Checks whether a given seed matches the wallet seed. The wallet must be - * unlocked prior to verification. - * - * @param {string} seed - Hexadecimal seed to be matched against the wallet data - * @returns True if input matches wallet seed - */ - verify (seed: string): Promise - /** - * Checks whether a given mnemonic phrase matches the wallet mnemonic. If a - * personal salt was used when generating the mnemonic, it cannot be verified. - * The wallet must be unlocked prior to verification. - * - * @param {string} mnemonic - Phrase to be matched against the wallet data - * @returns True if input matches wallet mnemonic - */ - verify (mnemonic: string): Promise -} diff --git a/test/main.test.mjs b/test/main.test.mjs index d59be6e..be4f2a5 100644 --- a/test/main.test.mjs +++ b/test/main.test.mjs @@ -4,18 +4,18 @@ import { failures, passes } from './GLOBALS.mjs' import './test.runner-check.mjs' -import './test.blake2b.mjs' -import './test.blocks.mjs' -import './test.calculate-pow.mjs' -import './test.create-wallet.mjs' -import './test.derive-accounts.mjs' -import './test.import-wallet.mjs' +// import './test.blake2b.mjs' +// import './test.blocks.mjs' +// import './test.calculate-pow.mjs' +// import './test.create-wallet.mjs' +// import './test.derive-accounts.mjs' +// import './test.import-wallet.mjs' import './test.ledger.mjs' -import './test.lock-unlock.mjs' -import './test.manage-rolodex.mjs' -import './test.refresh-accounts.mjs' -import './test.tools.mjs' -import './test.wallet-sign.mjs' +// import './test.lock-unlock.mjs' +// import './test.manage-rolodex.mjs' +// import './test.refresh-accounts.mjs' +// import './test.tools.mjs' +// import './test.wallet-sign.mjs' console.log('%cTESTING COMPLETE', 'color:orange;font-weight:bold') console.log('%cPASS: ', 'color:green;font-weight:bold', passes.length) diff --git a/test/perf.account.mjs b/test/perf.account.mjs index 90d8384..3f5880e 100644 --- a/test/perf.account.mjs +++ b/test/perf.account.mjs @@ -7,7 +7,7 @@ import { assert, isNode, stats, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/perf.block.mjs b/test/perf.block.mjs index 4bfcd89..27ca2b6 100644 --- a/test/perf.block.mjs +++ b/test/perf.block.mjs @@ -7,7 +7,7 @@ import { isNode, stats, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Block} +* @type {typeof import('../dist/types/main.d.ts').Block} */ let Block if (isNode) { diff --git a/test/perf.wallet.mjs b/test/perf.wallet.mjs index 6764e9e..c24075e 100644 --- a/test/perf.wallet.mjs +++ b/test/perf.wallet.mjs @@ -7,7 +7,7 @@ import { isNode, stats, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.blake2b.mjs b/test/test.blake2b.mjs index 7d4a832..73ba948 100644 --- a/test/test.blake2b.mjs +++ b/test/test.blake2b.mjs @@ -7,7 +7,7 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { BLAKE2B_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Blake2b} +* @type {typeof import('../dist/types/main.d.ts').Blake2b} */ let Blake2b if (isNode) { diff --git a/test/test.blocks.mjs b/test/test.blocks.mjs index c5d1924..9d723d8 100644 --- a/test/test.blocks.mjs +++ b/test/test.blocks.mjs @@ -7,15 +7,15 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Account} +* @type {typeof import('../dist/types/main.d.ts').Account} */ let Account /** -* @type {typeof import('../dist/types.d.ts').Block} +* @type {typeof import('../dist/types/main.d.ts').Block} */ let Block /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.calculate-pow.mjs b/test/test.calculate-pow.mjs index 6505228..b107628 100644 --- a/test/test.calculate-pow.mjs +++ b/test/test.calculate-pow.mjs @@ -7,11 +7,11 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Blake2b} +* @type {typeof import('../dist/types/main.d.ts').Blake2b} */ let Blake2b /** -* @type {typeof import('../dist/types.d.ts').Block} +* @type {typeof import('../dist/types/main.d.ts').Block} */ let Block if (isNode) { diff --git a/test/test.create-wallet.mjs b/test/test.create-wallet.mjs index 389830a..f6ed8ca 100644 --- a/test/test.create-wallet.mjs +++ b/test/test.create-wallet.mjs @@ -7,7 +7,7 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.derive-accounts.mjs b/test/test.derive-accounts.mjs index 713cc40..ec89557 100644 --- a/test/test.derive-accounts.mjs +++ b/test/test.derive-accounts.mjs @@ -7,7 +7,7 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.import-wallet.mjs b/test/test.import-wallet.mjs index 49fcf80..8c021a1 100644 --- a/test/test.import-wallet.mjs +++ b/test/test.import-wallet.mjs @@ -7,11 +7,11 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { BIP32_TEST_VECTORS, CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Account} +* @type {typeof import('../dist/types/main.d.ts').Account} */ let Account /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.ledger.mjs b/test/test.ledger.mjs index 46b1276..1ca0d67 100644 --- a/test/test.ledger.mjs +++ b/test/test.ledger.mjs @@ -7,23 +7,23 @@ import { assert, click, env, isNode, suite, test } from './GLOBALS.mjs' import { CUSTOM_TEST_VECTORS, NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Account} +* @type {typeof import('../dist/types/main.d.ts').Account} */ let Account /** -* @type {typeof import('../dist/types.d.ts').Block} +* @type {typeof import('../dist/types/main.d.ts').Block} */ let Block /** -* @type {typeof import('../dist/types.d.ts').Ledger} +* @type {typeof import('../dist/types/main.d.ts').Ledger} */ let Ledger /** -* @type {typeof import('../dist/types.d.ts').Rpc} +* @type {typeof import('../dist/types/main.d.ts').Rpc} */ let Rpc /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet diff --git a/test/test.lock-unlock.mjs b/test/test.lock-unlock.mjs index 5e403c3..8184959 100644 --- a/test/test.lock-unlock.mjs +++ b/test/test.lock-unlock.mjs @@ -7,11 +7,11 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS, TREZOR_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Account} +* @type {typeof import('../dist/types/main.d.ts').Account} */ let Account /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.manage-rolodex.mjs b/test/test.manage-rolodex.mjs index e0b8c75..c918c88 100644 --- a/test/test.manage-rolodex.mjs +++ b/test/test.manage-rolodex.mjs @@ -7,7 +7,7 @@ import { assert, isNode, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Rolodex} +* @type {typeof import('../dist/types/main.d.ts').Rolodex} */ let Rolodex /** diff --git a/test/test.refresh-accounts.mjs b/test/test.refresh-accounts.mjs index 56d3f4e..afc113b 100644 --- a/test/test.refresh-accounts.mjs +++ b/test/test.refresh-accounts.mjs @@ -7,15 +7,15 @@ import { assert, env, isNode, suite, test } from './GLOBALS.mjs' import { NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Account} +* @type {typeof import('../dist/types/main.d.ts').Account} */ let Account /** -* @type {typeof import('../dist/types.d.ts').Rpc} +* @type {typeof import('../dist/types/main.d.ts').Rpc} */ let Rpc /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) { diff --git a/test/test.tools.mjs b/test/test.tools.mjs index cb7e312..f73e385 100644 --- a/test/test.tools.mjs +++ b/test/test.tools.mjs @@ -7,23 +7,23 @@ import { assert, env, isNode, suite, test } from './GLOBALS.mjs' import { MAX_RAW, MAX_SUPPLY, NANO_TEST_VECTORS } from './VECTORS.mjs' /** -* @type {typeof import('../dist/types.d.ts').Account} +* @type {typeof import('../dist/types/main.d.ts').Account} */ let Account /** -* @type {typeof import('../dist/types.d.ts').Block} +* @type {typeof import('../dist/types/main.d.ts').Block} */ let Block /** -* @type {typeof import('../dist/types.d.ts').Rpc} +* @type {typeof import('../dist/types/main.d.ts').Rpc} */ let Rpc /** -* @type {typeof import('../dist/types.d.ts').Tools} +* @type {typeof import('../dist/types/main.d.ts').Tools} */ let Tools /** -* @type {typeof import('../dist/types.d.ts').Wallet} +* @type {typeof import('../dist/types/main.d.ts').Wallet} */ let Wallet if (isNode) {