diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b7ba514..0000000 --- a/.eslintignore +++ /dev/null @@ -1,3 +0,0 @@ -/dist -/node_modules -.eslintrc.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 1bd1c24..0000000 --- a/.eslintrc +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": [ "@generalprotocols/eslint-config/typescript" ], - "parserOptions": - { - "project": "./tsconfig.json", - "ecmaVersion": "2018", - "sourceType": "module" - }, - "env": - { - "jest": true - } -} diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index df30ba9..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,65 +0,0 @@ -module.exports = { - // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy - // This option interrupts the configuration hierarchy at this file - // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) - root: true, - - // https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser - // Must use parserOptions instead of "parser" to allow vue-eslint-parser to keep working - // `parser: 'vue-eslint-parser'` is already included with any 'plugin:vue/**' config and should be omitted - parserOptions: { - parser: require.resolve('@typescript-eslint/parser'), - extraFileExtensions: [ '.vue' ] - }, - - env: { - browser: true, - es2021: true, - node: true, - }, - - // Rules order is important, please avoid shuffling them - extends: [ - // Base ESLint recommended rules - // 'eslint:recommended', - - // https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage - // ESLint typescript rules - 'plugin:@typescript-eslint/recommended', - - // https://github.com/prettier/eslint-config-prettier#installation - // usage with Prettier, provided by 'eslint-config-prettier'. - 'prettier' - ], - - plugins: [ - // required to apply rules which need type information - '@typescript-eslint', - - // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 - // Prettier has not been included as plugin to avoid performance impact - // add it as an extension for your IDE - - ], - - // add your custom rules here - rules: { - - 'prefer-promise-reject-errors': 'off', - - quotes: ['warn', 'single', { avoidEscape: true }], - - // this rule, if on, would require explicit return type on the `render` function - '@typescript-eslint/explicit-function-return-type': 'off', - - // in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled - '@typescript-eslint/no-var-requires': 'off', - - // The core 'no-unused-vars' rules (in the eslint:recommended ruleset) - // does not work with type definitions - 'no-unused-vars': 'off', - - // allow debugger during development only - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' - } -} diff --git a/.gitignore b/.gitignore index 57f240d..d7cdc7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,20 @@ -node_modules/ -.nyc_output/ -dist/ -coverage/ -.vscode -yarn.lock -yarn-error.log -unit-tests.xml -e2e-tests.xml +# Dependencies +**/node_modules -# Ignore Vitepress builds/cache/generated-API. -docs/.vitepress/dist -docs/.vitepress/cache -docs/api +# Testing and coverage +**/.nyc_output +**/coverage +**/unit-tests.xml +**/e2e-tests.xml -# Ignore any lerna debug output. -lerna-debug.log +# Build outputs +**/dist +**/tsconfig.tsbuildinfo + +# Editor +**/.vscode + +# Logs and debug files +**/yarn.lock +**/yarn-error.log +**/lerna-debug.log diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d49a889..4f74078 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,9 +23,9 @@ pages: stage: publish script: - npm run docs:build - - mv docs/.vitepress/dist public + - mv packages/docs/dist public artifacts: paths: - public - only: - - main +# only: +# - main diff --git a/README.md b/README.md index 9bcb023..ab46c6a 100644 --- a/README.md +++ b/README.md @@ -72,11 +72,13 @@ npm run dev ## Useful NPM Workspace Commands - List all packages in the workspace: + ```bash npm ls -w ``` - Run a command in a specific package: + ```bash npm run --workspace=@my-monorepo/ ``` @@ -85,3 +87,17 @@ npm run dev ```bash npm install --workspace=@my-monorepo/ ``` + +## Refactor details + +We will want to use inheritance in place of IWallet, etc. A neat structure to refactor to is: + +- /theThing/ +- base-thing.ts +- types.ts +- utils.ts +- ... + +- Rename IndexedCollection to ExtMap. +- Rename from method to fromArray. +- Have the from method accept an object. diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts deleted file mode 100644 index 07df859..0000000 --- a/docs/.vitepress/config.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { defineConfig } from 'vitepress'; -import typedocSidebar from '../api/typedoc-sidebar.json'; - -function capitalizeFirstLetter(str: string) { - return str.charAt(0).toUpperCase() + str.slice(1); -} - -// NOTE: We can filter the generated sidebar to produce relevant links to documentation. -// See the contents of the typedocSidear import for structure. -function classesOnlySidebar() { - return typedocSidebar.map((rootItem) => { - const srcItem = rootItem.items.find( - (childItem) => childItem.text === 'src' - ); - - const classes = srcItem?.items.find( - (childItem) => childItem.text === 'Classes' - ); - - return { - ...rootItem, - text: capitalizeFirstLetter(rootItem.text), - items: classes?.items, - }; - }); -} - - -// https://vitepress.dev/reference/site-config -export default defineConfig({ - title: 'Documentation', - description: 'Documentation and guide for the XO Stack', - themeConfig: { - // Set the logo - logo: { - light: '/logo-light.svg', - dark: '/logo-dark.svg', - }, - - // Enable search functionality. - search: { - provider: 'local', - }, - - // https://vitepress.dev/reference/default-theme-config - nav: [ - { text: 'Home', link: '/' }, - { text: 'Tools', link: '/tools' }, - ], - - sidebar: [ - { - text: 'Guide', - items: [ - { text: 'Quick Start', link: '/markdown-examples' }, - { text: 'Best Practices', link: '/api-examples' }, - ], - }, - { - text: 'Frameworks', - items: [ - { text: 'Vue', link: '/frameworks/vue' }, - { text: 'React', link: '/frameworks/react' }, - { text: 'Svelte', link: '/frameworks/svelte' }, - ], - }, - { - text: 'API', - // NOTE: In future, we may want to manually specify API items in the sidebar. - // This would allow us to keep some experimental things undocumented. - // But, for now, let's use an autogenerated sidebar so that we don't have to maintain this. - // We can polish it later. - items: classesOnlySidebar(), - }, - ], - - socialLinks: [ - { icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' }, - ], - - // This is the outline on the right-hand side of the page. - outline: { - level: [2, 3], - label: 'On this page', - }, - }, - - head: [['link', { rel: 'icon', href: '/favicon.png' }]] - -}); - - diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css deleted file mode 100644 index 822344e..0000000 --- a/docs/.vitepress/theme/style.css +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Customize default theme styling by overriding CSS variables: - * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css - */ - -/** - * Colors - * - * Each colors have exact same color scale system with 3 levels of solid - * colors with different brightness, and 1 soft color. - * - * - `XXX-1`: The most solid color used mainly for colored text. It must - * satisfy the contrast ratio against when used on top of `XXX-soft`. - * - * - `XXX-2`: The color used mainly for hover state of the button. - * - * - `XXX-3`: The color for solid background, such as bg color of the button. - * It must satisfy the contrast ratio with pure white (#ffffff) text on - * top of it. - * - * - `XXX-soft`: The color used for subtle background such as custom container - * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors - * on top of it. - * - * The soft color must be semi transparent alpha channel. This is crucial - * because it allows adding multiple "soft" colors on top of each other - * to create a accent, such as when having inline code block inside - * custom containers. - * - * - `default`: The color used purely for subtle indication without any - * special meanings attched to it such as bg color for menu hover state. - * - * - `brand`: Used for primary brand colors, such as link text, button with - * brand theme, etc. - * - * - `tip`: Used to indicate useful information. The default theme uses the - * brand color for this by default. - * - * - `warning`: Used to indicate warning to the users. Used in custom - * container, badges, etc. - * - * - `danger`: Used to show error, or dangerous message to the users. Used - * in custom container, badges, etc. - * -------------------------------------------------------------------------- */ - -:root { - --vp-c-default-1: var(--vp-c-gray-1); - --vp-c-default-2: var(--vp-c-gray-2); - --vp-c-default-3: var(--vp-c-gray-3); - --vp-c-default-soft: var(--vp-c-gray-soft); - - --vp-c-brand-1: var(--vp-c-indigo-1); - --vp-c-brand-2: var(--vp-c-indigo-2); - --vp-c-brand-3: var(--vp-c-indigo-3); - --vp-c-brand-soft: var(--vp-c-indigo-soft); - - --vp-c-tip-1: var(--vp-c-brand-1); - --vp-c-tip-2: var(--vp-c-brand-2); - --vp-c-tip-3: var(--vp-c-brand-3); - --vp-c-tip-soft: var(--vp-c-brand-soft); - - --vp-c-warning-1: var(--vp-c-yellow-1); - --vp-c-warning-2: var(--vp-c-yellow-2); - --vp-c-warning-3: var(--vp-c-yellow-3); - --vp-c-warning-soft: var(--vp-c-yellow-soft); - - --vp-c-danger-1: var(--vp-c-red-1); - --vp-c-danger-2: var(--vp-c-red-2); - --vp-c-danger-3: var(--vp-c-red-3); - --vp-c-danger-soft: var(--vp-c-red-soft); -} - -/** - * Component: Button - * -------------------------------------------------------------------------- */ - -:root { - --vp-button-brand-border: transparent; - --vp-button-brand-text: var(--vp-c-white); - --vp-button-brand-bg: var(--vp-c-brand-3); - --vp-button-brand-hover-border: transparent; - --vp-button-brand-hover-text: var(--vp-c-white); - --vp-button-brand-hover-bg: var(--vp-c-brand-2); - --vp-button-brand-active-border: transparent; - --vp-button-brand-active-text: var(--vp-c-white); - --vp-button-brand-active-bg: var(--vp-c-brand-1); -} - -/** - * Component: Home - * -------------------------------------------------------------------------- */ - -:root { - --vp-home-hero-name-color: transparent; - --vp-home-hero-name-background: -webkit-linear-gradient( - 120deg, - #38b6ff 30%, - #5271ff - ); - - /* - - --vp-home-hero-image-background-image: linear-gradient( - -45deg, - #5271ff 50%, - #38b6ff 50% - - ); - - --vp-home-hero-image-filter: blur(44px); - - */ -} - -@media (min-width: 640px) { - :root { - --vp-home-hero-image-filter: blur(56px); - } -} - -@media (min-width: 960px) { - :root { - --vp-home-hero-image-filter: blur(68px); - } -} - -/** - * Component: Custom Block - * -------------------------------------------------------------------------- */ - -:root { - --vp-custom-block-tip-border: transparent; - --vp-custom-block-tip-text: var(--vp-c-text-1); - --vp-custom-block-tip-bg: var(--vp-c-brand-soft); - --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); -} - -/** - * Component: Algolia - * -------------------------------------------------------------------------- */ - -.DocSearch { - --docsearch-primary-color: var(--vp-c-brand-1) !important; -} diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index f2afd14..0000000 --- a/docs/index.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -# VitePress configuration -layout: home - -hero: - name: "XO Stack" - text: "Documentation" - tagline: Building smart applications on the Bitcoin Cash UTXO Network - actions: - - theme: brand - text: Quick Start - link: /markdown-examples.html - - theme: brand - text: GitHub - link: https://gitlab.com/GeneralProtocols/xo/stack - -features: - - icon: - light: /icons/api-light.svg - dark: /icons/api-dark.svg - title: API - details: Access powerful APIs to build smart apps on the BCH UTXO Network. - - icon: - light: /icons/framework-light.svg - dark: /icons/framework-dark.svg - title: Framework - details: A solid framework for building scalable applications. - - icon: - light: /icons/tools-light.svg - dark: /icons/tools-dark.svg - title: Tools - details: Essential tools to streamline development and deployment. - ---- - - -
-

Developed by:

-
-
- - GP Logo - GP Logo - -
diff --git a/docs/markdown-examples.md b/docs/markdown-examples.md deleted file mode 100644 index f9258a5..0000000 --- a/docs/markdown-examples.md +++ /dev/null @@ -1,85 +0,0 @@ -# Markdown Extension Examples - -This page demonstrates some of the built-in markdown extensions provided by VitePress. - -## Syntax Highlighting - -VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: - -**Input** - -````md -```js{4} -export default { - data () { - return { - msg: 'Highlighted!' - } - } -} -``` -```` - -**Output** - -```js{4} -export default { - data () { - return { - msg: 'Highlighted!' - } - } -} -``` - -## Custom Containers - -**Input** - -```md -::: info -This is an info box. -::: - -::: tip -This is a tip. -::: - -::: warning -This is a warning. -::: - -::: danger -This is a dangerous warning. -::: - -::: details -This is a details block. -::: -``` - -**Output** - -::: info -This is an info box. -::: - -::: tip -This is a tip. -::: - -::: warning -This is a warning. -::: - -::: danger -This is a dangerous warning. -::: - -::: details -This is a details block. -::: - -## More - -Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/docs/tools/index.md b/docs/tools/index.md deleted file mode 100644 index 9f454a2..0000000 --- a/docs/tools/index.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tools - -May want to have a Tools/Sandbox section. - -I'd leave until later before exploring this more, but it sounds like we can (technically) create custom components. - -https://vitepress.dev/guide/using-vue diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..d1b1845 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,34 @@ +// @ts-check + +import eslint from '@eslint/js'; +import prettierConfig from 'eslint-config-prettier'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + prettierConfig, + { + // Ignore the following directory patterns. + ignores: [ + '**/dist/**', + '**/docs/**', + '**/examples/**', + '**/experiments/**', + '**/node_modules/**', + ], + }, + { + rules: { + // Allow unused variables if they're prefixed with '_'. + '@typescript-eslint/no-unused-vars': [ + 'warn', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrorsIgnorePattern: '^_', + }, + ], + }, + }, +); diff --git a/examples/node/benchmark-sha256.js b/examples/node/benchmark-sha256.js new file mode 100644 index 0000000..e591c17 --- /dev/null +++ b/examples/node/benchmark-sha256.js @@ -0,0 +1,15 @@ +import { Bytes, Crypto } from '@xo/primitives'; + +const payload = Bytes.fromHex( + '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff', +); + +console.time('sha256'); + +for (let i = 0; i < 10_000; i++) { + Crypto.sha256(payload.toBytes()); +} + +// NOTE: Around 100ms for 10,000 SHA256's on my machine. +// Not too bad, but might still be worth avoiding if trivial. +console.timeEnd('sha256'); diff --git a/examples/node/genesisTest.js b/examples/node/genesisTest.js index 5beb292..e51ffd7 100644 --- a/examples/node/genesisTest.js +++ b/examples/node/genesisTest.js @@ -42,18 +42,18 @@ await blockchainLocal.broadcastTransaction(coinbaseTransaction, { console.log('walletP2PKH unspents', await walletP2PKH.fetchUnspentDirectives()); console.log( 'walletP2PKHReceive unspents', - await walletP2PKHReceive.fetchUnspentDirectives() + await walletP2PKHReceive.fetchUnspentDirectives(), ); const sweepTx = await walletP2PKH.sweep( - walletP2PKHReceive.getAddress().toLockscriptBytes() + walletP2PKHReceive.getAddress().toLockscriptBytes(), ); await blockchainLocal.broadcastTransaction(sweepTx); console.log('walletP2PKH unspents', await walletP2PKH.fetchUnspentDirectives()); console.log( 'walletP2PKHReceive unspents', - await walletP2PKHReceive.fetchUnspentDirectives() + await walletP2PKHReceive.fetchUnspentDirectives(), ); console.log(blockchainLocal.deriveUtxoSet()); diff --git a/examples/node/index.js b/examples/node/index.js index e90a7d4..24dd94b 100644 --- a/examples/node/index.js +++ b/examples/node/index.js @@ -4,14 +4,14 @@ import { Blockchain, WalletHD } from '@xo/wallet'; const blockchain = new Blockchain( ['bch.imaginary.cash', 'cashnode.bch.ninja', 'electrum.imaginary.cash'], 'Test App', - '1.5' + '1.5', ); await blockchain.start(); const wallet = WalletHD.fromMnemonic( 'harsh gospel job ordinary garden clay eight cactus replace general prison permit', - blockchain + blockchain, ); await wallet.scan(); diff --git a/examples/node/storageTest.js b/examples/node/storageTest.js new file mode 100644 index 0000000..244a2b5 --- /dev/null +++ b/examples/node/storageTest.js @@ -0,0 +1,48 @@ +import { BlockchainElectrum, StorageMemory, WalletHD } from '@xo/wallet'; + +const ELECTRUM_SERVERS = [ + 'bch.imaginary.cash', + 'cashnode.bch.ninja', + 'electrum.imaginary.cash', +]; + +const blockchainElectrum = new BlockchainElectrum(ELECTRUM_SERVERS); +await blockchainElectrum.start(); + +const storage = new StorageMemory(); + +const walletStore = await storage.getChild('wallets'); +const walletOneStore = await walletStore.getChild('1'); +console.log(walletOneStore); + +const cache = new StorageMemory(); + +const services = { + blockchain: blockchainElectrum, + storage: walletOneStore, + cache: cache, +}; + +const wallet = await WalletHD.from( + { + mnemonic: + 'grape amazing region hand script truck veteran tilt gaze rough despair guard', + }, + blockchain, + walletOneStore, + cache, +); + +await wallet.start(); + +console.log(await storage.export()); + +const txs = await wallet.fetchTransactions(); + +console.log(txs); + +const wallet2 = await WalletHD.fromStorage(services); + +await wallet2.start(); + +console.log(await wallet2.fetchTransactions()); diff --git a/examples/node/txBuilderTest.js b/examples/node/txBuilderTest.js new file mode 100644 index 0000000..af6d4ec --- /dev/null +++ b/examples/node/txBuilderTest.js @@ -0,0 +1,72 @@ +import { Transaction } from '@xo/primitives'; +import { BlockchainLocal, WalletP2PKH } from '@xo/wallet'; + +const blockchainLocal = new BlockchainLocal(); +await blockchainLocal.start(); + +const services = { + blockchain: blockchainLocal, +}; + +const walletP2PKH = WalletP2PKH.generateRandom(services); +const walletP2PKHReceive = WalletP2PKH.generateRandom(services); + +const coinbaseTransaction = new Transaction({ + version: 2, + locktime: 0, + inputs: [ + { + outpointIndex: 0, + outpointTransactionHash: new Uint8Array(), + unlockingBytecode: new Uint8Array(), + sequenceNumber: 0, + }, + ], + outputs: [ + { + lockingBytecode: ( + await walletP2PKH.getReceivingAddress() + ).toLockscriptBytes(), + valueSatoshis: 100_000n, + }, + { + lockingBytecode: ( + await walletP2PKH.getReceivingAddress() + ).toLockscriptBytes(), + valueSatoshis: 150_000n, + }, + ], +}); + +await blockchainLocal.broadcastTransaction(coinbaseTransaction, { + skipValidation: true, +}); + +console.log('walletP2PKH unspents', await walletP2PKH.fetchUnspentDirectives()); +console.log( + 'walletP2PKHReceive unspents', + await walletP2PKHReceive.fetchUnspentDirectives(), +); + +const sendTx = await walletP2PKH.send({ + outputs: [ + { + lockingBytecode: ( + await walletP2PKHReceive.getReceivingAddress() + ).toLockscriptBytes(), + valueSatoshis: 150_000n, + }, + ], +}); + +console.log(sendTx); + +// await blockchainLocal.broadcastTransaction(new Transaction(sendTx)); + +console.log('walletP2PKH unspents', await walletP2PKH.fetchUnspentDirectives()); +console.log( + 'walletP2PKHReceive unspents', + await walletP2PKHReceive.fetchUnspentDirectives(), +); + +console.log(blockchainLocal.deriveUtxoSet()); diff --git a/experiments/react/wallet-hd-react.ts b/experiments/react/wallet-hd-react.ts index d963c52..36bd714 100644 --- a/experiments/react/wallet-hd-react.ts +++ b/experiments/react/wallet-hd-react.ts @@ -13,7 +13,7 @@ class WalletHDReact extends WalletHD { node: HdPrivateNodeValid, electrum: Blockchain, walletFactory = (privateKeyBytes: Uint8Array, electrum: Blockchain) => - new WalletP2PKHReact(privateKeyBytes, electrum) + new WalletP2PKHReact(privateKeyBytes, electrum), ) { super(node, electrum, walletFactory); @@ -66,17 +66,17 @@ class WalletHDReact extends WalletHD { export function useWalletHD(node: HdPrivateNodeValid, electrum: Blockchain) { const walletHD = useMemo( () => new WalletHDReact(node, electrum), - [node, electrum] + [node, electrum], ); const wallets = useSyncExternalStore( walletHD.subscribeToWallets, - walletHD.getWallets + walletHD.getWallets, ); const shouldMonitor = useSyncExternalStore( walletHD.subscribeToShouldMonitor, - walletHD.getShouldMonitor + walletHD.getShouldMonitor, ); const balance = useMemo(() => { diff --git a/experiments/react/wallet-p2pkh-react.ts b/experiments/react/wallet-p2pkh-react.ts index 563b1de..b0c0391 100644 --- a/experiments/react/wallet-p2pkh-react.ts +++ b/experiments/react/wallet-p2pkh-react.ts @@ -8,21 +8,21 @@ import { export function useWalletP2PKH( privateKeyBytes: Uint8Array, - electrum: Blockchain + electrum: Blockchain, ) { const wallet = React.useMemo( () => new WalletP2PKHReact(privateKeyBytes, electrum), - [privateKeyBytes, electrum] + [privateKeyBytes, electrum], ); const unspents = useSyncExternalStore( wallet.subscribeToUnspents, - wallet.getUnspents + wallet.getUnspents, ); const transactions = useSyncExternalStore( wallet.subscribeToTransactions, - wallet.getTransactions + wallet.getTransactions, ); const balance = React.useMemo(() => { diff --git a/experiments/svelte/svelte.ts b/experiments/svelte/svelte.ts index a8b2fca..992619f 100644 --- a/experiments/svelte/svelte.ts +++ b/experiments/svelte/svelte.ts @@ -12,7 +12,7 @@ export class WalletP2PKHSvelte extends WalletP2PKH { // Derived store for balance public balance: Readable = derived(this.unspents, ($unspents) => - $unspents.reduce((total, unspent) => total + unspent.value, 0) + $unspents.reduce((total, unspent) => total + unspent.value, 0), ); constructor(privateKeyBytes: Uint8Array, electrum: Blockchain) { @@ -28,7 +28,7 @@ export class WalletP2PKHSvelte extends WalletP2PKH { export function useWalletP2PKHSvelte( privateKeyBytes: Uint8Array, - electrum: Blockchain + electrum: Blockchain, ): WalletP2PKHSvelte { return new WalletP2PKHSvelte(privateKeyBytes, electrum); } @@ -44,18 +44,18 @@ export class WalletHDSvelte extends WalletHD { let walletBalance = 0; wallet.balance.subscribe((value) => (walletBalance = value))(); return total + walletBalance; - }, 0) + }, 0), ); constructor( node: HdPrivateNodeValid, electrum: Blockchain, - walletFactory = useWalletP2PKHSvelte + walletFactory = useWalletP2PKHSvelte, ) { super(node, electrum, walletFactory); this.events.on('walletsUpdated', (wallets) => this.wallets.set(wallets)); this.events.on('shouldMonitorUpdated', (shouldMonitor) => - this.shouldMonitor.set(shouldMonitor) + this.shouldMonitor.set(shouldMonitor), ); } } diff --git a/lerna.json b/lerna.json index 9a4d59b..a293d19 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", - "packages": [ - "packages/*" - ], + "packages": ["packages/*"], "version": "0.0.0" } diff --git a/package-lock.json b/package-lock.json index 5290207..485e361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,51 +15,54 @@ "@bitauth/libauth": "^3.0.0" }, "devDependencies": { + "@eslint/js": "^9.10.0", + "@types/eslint__js": "^8.42.3", "@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/parser": "^8.5.0", + "@vitest/coverage-v8": "^2.1.6", "eslint": "^9.10.0", - "eslint-config-prettier": "^8.1.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-prettier": "^5.2.1", "lerna": "^8.1.8", - "prettier": "^2.5.1", - "typedoc": "^0.26.6", - "typedoc-plugin-markdown": "^4.2.6", - "typedoc-vitepress-theme": "^1.0.1", - "typescript": "^5.1.3", - "vitepress": "^1.3.4" + "madge": "^8.0.0", + "prettier": "^3.3.3", + "typescript": "5.7.2", + "typescript-eslint": "^8.6.0", + "vitest": "^2.1.6" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", - "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", - "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", - "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.9.3" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -67,9 +70,9 @@ } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", - "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -77,322 +80,221 @@ "algoliasearch": ">= 4.9.1 < 6" } }, - "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.24.0.tgz", - "integrity": "sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==", + "node_modules/@algolia/client-abtesting": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.17.1.tgz", + "integrity": "sha512-Os/xkQbDp5A5RdGYq1yS3fF69GoBJH5FIfrkVh+fXxCSe714i1Xdl9XoXhS4xG76DGKm6EFMlUqP024qjps8cg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.24.0" - } - }, - "node_modules/@algolia/cache-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.24.0.tgz", - "integrity": "sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@algolia/cache-in-memory": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.24.0.tgz", - "integrity": "sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/cache-common": "4.24.0" - } - }, - "node_modules/@algolia/client-account": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.24.0.tgz", - "integrity": "sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.24.0.tgz", - "integrity": "sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.17.1.tgz", + "integrity": "sha512-WKpGC+cUhmdm3wndIlTh8RJXoVabUH+4HrvZHC4hXtvCYojEXYeep8RZstatwSZ7Ocg6Y2u67bLw90NEINuYEw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-analytics/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/client-analytics/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.2.3.tgz", - "integrity": "sha512-zqfcbgjYR72Y/rx/+/6g5Li/eV33yhRq5mkGbU06JYBzvGq6viy0gZl1ckCFhLLifKzXZ4yzUQTw/KG6FV+smg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.17.1.tgz", + "integrity": "sha512-5rb5+yPIie6912riAypTSyzbE23a7UM1UpESvD8GEPI4CcWQvA9DBlkRNx9qbq/nJ5pvv8VjZjUxJj7rFkzEAA==", "dev": true, "license": "MIT", - "peer": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.17.1.tgz", + "integrity": "sha512-nb/tfwBMn209TzFv1DDTprBKt/wl5btHVKoAww9fdEVdoKK02R2KAqxe5tuXLdEzAsS+LevRyOM/YjXuLmPtjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.24.0.tgz", - "integrity": "sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.17.1.tgz", + "integrity": "sha512-JuNlZe1SdW9KbV0gcgdsiVkFfXt0mmPassdS3cBSGvZGbPB9JsHthD719k5Y6YOY4dGvw1JmC1i9CwCQHAS8hg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, - "node_modules/@algolia/client-personalization/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", + "node_modules/@algolia/client-query-suggestions": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.17.1.tgz", + "integrity": "sha512-RBIFIv1QE3IlAikJKWTOpd6pwE4d2dY6t02iXH7r/SLXWn0HzJtsAPPeFg/OKkFvWAXt0H7In2/Mp7a1/Dy2pw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.2.3.tgz", - "integrity": "sha512-xXdCg8vpiwE8gqSyvjxq8V3qbFa+gHasY5epIz718IByWv3WKLLi/n4SMIfB/zRwXTLVWeGOH/UJSz5VCnAAqg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.17.1.tgz", + "integrity": "sha512-bd5JBUOP71kPsxwDcvOxqtqXXVo/706NFifZ/O5Rx5GB8ZNVAhg4l7aGoT6jBvEfgmrp2fqPbkdIZ6JnuOpGcw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@algolia/client-common": "5.2.3", - "@algolia/requester-browser-xhr": "5.2.3", - "@algolia/requester-node-http": "5.2.3" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@algolia/logger-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.24.0.tgz", - "integrity": "sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@algolia/logger-console": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.24.0.tgz", - "integrity": "sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==", + "node_modules/@algolia/ingestion": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.17.1.tgz", + "integrity": "sha512-T18tvePi1rjRYcIKhd82oRukrPWHxG/Iy1qFGaxCplgRm9Im5z96qnYOq75MSKGOUHkFxaBKJOLmtn8xDR+Mcw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/logger-common": "4.24.0" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.17.1.tgz", + "integrity": "sha512-gDtow+AUywTehRP8S1tWKx2IvhcJOxldAoqBxzN3asuQobF7er5n72auBeL++HY4ImEuzMi7PDOA/Iuwxs2IcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.24.0.tgz", - "integrity": "sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.17.1.tgz", + "integrity": "sha512-2992tTHkRe18qmf5SP57N78kN1D3e5t4PO1rt10sJncWtXBZWiNOK6K/UcvWsFbNSGAogFcIcvIMAl5mNp6RWA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/@algolia/recommend/node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-common": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.2.3.tgz", - "integrity": "sha512-lezcE4E7ax7JkDGDKA/xAnyAY9p9LZ4AxzsyL0pksqUpOvn4U0msP553M2yJRfsxxdGDp15noCnPuRsh7u8dMg==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.17.1.tgz", + "integrity": "sha512-XpKgBfyczVesKgr7DOShNyPPu5kqlboimRRPjdqAw5grSyHhCmb8yoTIKy0TCqBABZeXRPMYT13SMruUVRXvHA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@algolia/client-common": "5.2.3" + "@algolia/client-common": "5.17.1" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@algolia/requester-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.24.0.tgz", - "integrity": "sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==", + "node_modules/@algolia/requester-fetch": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.17.1.tgz", + "integrity": "sha512-EhUomH+DZP5vb6DnEjT0GvXaXBSwzZnuU6hPGNU1EYKRXDouRjII/bIWpVjt7ycMgL2D2oQruqDh6rAWUhQwRw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" + } }, "node_modules/@algolia/requester-node-http": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.2.3.tgz", - "integrity": "sha512-xTxsRnJqxG1dylIkxmflrHO9LJfJKjSHqEF5yGdRrtnqIEvb2hiQPCHm2XwqxMa3NBcf6lmydGfJqhPLnRJwtw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.17.1.tgz", + "integrity": "sha512-PSnENJtl4/wBWXlGyOODbLYm6lSiFqrtww7UpQRCJdsHXlJKF8XAP6AME8NxvbE0Qo/RJUxK0mvyEh9sQcx6bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@algolia/client-common": "5.2.3" + "@algolia/client-common": "5.17.1" }, "engines": { "node": ">= 14.0.0" } }, - "node_modules/@algolia/transporter": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.24.0.tgz", - "integrity": "sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@algolia/cache-common": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/requester-common": "4.24.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", + "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, @@ -400,91 +302,34 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -494,19 +339,26 @@ } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@bitauth/libauth": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@bitauth/libauth/-/libauth-3.0.0.tgz", @@ -516,35 +368,49 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/@dependents/detective-less": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.0.tgz", + "integrity": "sha512-D/9dozteKcutI5OdxJd8rU+fL6XgaaRg60sPPJWkT33OCiRfkCu5wO5B/yXTaaL2e6EB0lcCBGe5E0XscZCvvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@docsearch/css": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.1.tgz", - "integrity": "sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.0.tgz", + "integrity": "sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==", "dev": true, "license": "MIT" }, "node_modules/@docsearch/js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.1.tgz", - "integrity": "sha512-erI3RRZurDr1xES5hvYJ3Imp7jtrXj6f1xYIzDzxiS7nNBufYWPbJwrmMqWC5g9y165PmxEmN9pklGCdLi0Iqg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.0.tgz", + "integrity": "sha512-PVuV629f5UcYRtBWqK7ID6vNL5647+2ADJypwTjfeBIrJfwPuHtzLy39hMGMfFK+0xgRyhTR0FZ83EkdEraBlg==", "dev": true, "license": "MIT", "dependencies": { - "@docsearch/react": "3.6.1", + "@docsearch/react": "3.8.0", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.1.tgz", - "integrity": "sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.0.tgz", + "integrity": "sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/autocomplete-core": "1.9.3", - "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.6.1", - "algoliasearch": "^4.19.1" + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.0", + "algoliasearch": "^5.12.0" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 19.0.0", @@ -567,24 +433,70 @@ } } }, - "node_modules/@electrum-cash/network": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@electrum-cash/network/-/network-3.3.0.tgz", - "integrity": "sha512-j0Hc0Q4P790KAhc7wl2u2FauJZt5KqnMotK83ripBtjmOdjHQ39OWL00v/KKzW80wgjL+pWiCxLvkEzpiSxXcg==", + "node_modules/@electrum-cash/debug-logs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@electrum-cash/debug-logs/-/debug-logs-1.0.0.tgz", + "integrity": "sha512-GU/CvRR9lZ0d8gy9CXGW7f//OHCIydBavv9q+JcxjGj8Xr7HwlGqHx+Wzhx9y3YmJrXfExpgClcd++gTjdEmzA==", "license": "MIT", "dependencies": { + "debug": "^4.3.7" + } + }, + "node_modules/@electrum-cash/network": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@electrum-cash/network/-/network-4.1.1.tgz", + "integrity": "sha512-v5abF2qGRTnBoi9tcS/iz7j82D8HYsK9iY0NM5v8/Qu8SnlMGGNz8UDFl+YzRPFXb4SUL3K0uf3Oydy82DB3oA==", + "license": "MIT", + "dependencies": { + "@electrum-cash/debug-logs": "^1.0.0", + "@electrum-cash/web-socket": "^1.0.0", + "async-mutex": "^0.5.0", + "debug": "^4.3.2", + "eventemitter3": "^5.0.1", + "lossless-json": "^4.0.1" + } + }, + "node_modules/@electrum-cash/network/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/@electrum-cash/protocol": { + "version": "2.1.0-development.8075178089", + "resolved": "https://registry.npmjs.org/@electrum-cash/protocol/-/protocol-2.1.0-development.8075178089.tgz", + "integrity": "sha512-CUEmneNRR9Eqkfx0OVFz/AQWLxODKCCjE7qYN1OPrchhDd1ZUqvk1UyUeiIwqrdfkVWrgLjm5NsRCBW0+r+lCg==", + "license": "MIT", + "dependencies": { + "@bitauth/libauth": "^3.0.0", + "@electrum-cash/network": "^4.1.0" + } + }, + "node_modules/@electrum-cash/web-socket": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@electrum-cash/web-socket/-/web-socket-1.0.0.tgz", + "integrity": "sha512-+VQ6aPE7nUysyDn9SB7/uqKuVJmjrhvr0LRK2ANTeR1DfmXBnv5z29e/0zK5A7ZoAz0gdZZuLDzN46r8S5Cxig==", + "license": "MIT", + "dependencies": { + "@electrum-cash/debug-logs": "^1.0.0", "@monsterbitar/isomorphic-ws": "^5.3.0", "@types/ws": "^8.5.5", - "async-mutex": "^0.4.0", - "debug": "^4.3.2", - "lossless-json": "^2.0.11", + "async-mutex": "^0.5.0", + "eventemitter3": "^5.0.1", + "lossless-json": "^4.0.1", "ws": "^8.13.0" } }, + "node_modules/@electrum-cash/web-socket/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/@emnapi/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.2.0.tgz", - "integrity": "sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", + "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", "dev": true, "license": "MIT", "dependencies": { @@ -593,9 +505,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", - "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", "dev": true, "license": "MIT", "dependencies": { @@ -1004,25 +916,28 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", "dev": true, "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", "engines": { @@ -1030,13 +945,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.5", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1068,10 +983,23 @@ "node": "*" } }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, "license": "MIT", "dependencies": { @@ -1117,9 +1045,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", - "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "engines": { @@ -1127,9 +1055,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1137,9 +1065,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", - "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1149,12 +1077,55 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "node_modules/@gerrit0/mini-shiki": { + "version": "1.24.4", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.24.4.tgz", + "integrity": "sha512-YEHW1QeAg6UmxEmswiQbOVEg1CW22b1XUD/lNTliOsu0LD0wqoyleFMnmbTp697QE0pcadQiR5cVtbbAPncvpw==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "@shikijs/engine-oniguruma": "^1.24.2", + "@shikijs/types": "^1.24.2", + "@shikijs/vscode-textmate": "^9.3.1" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", @@ -1171,9 +1142,9 @@ } }, "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1194,6 +1165,23 @@ "node": ">=6.9.0" } }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.16.tgz", + "integrity": "sha512-mnQ0Ih8CDIgOqbi0qz01AJNOeFVuGFRimelg3JmJtD0y5EpZVw+enPPcpcxJKipsRZ/oqhcP3AhYkF1kM7yomg==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1213,9 +1201,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, "license": "MIT", "engines": { @@ -1304,6 +1292,16 @@ "dev": true, "license": "ISC" }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -1317,23 +1315,70 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@lerna/create": { - "version": "8.1.8", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-8.1.8.tgz", - "integrity": "sha512-wi72R01tgjBjzG2kjRyTHl4yCTKDfDMIXRyKz9E/FBa9SkFvUOAE4bdyY9MhEsRZmSWL7+CYE8Flv/HScRpBbA==", + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@lerna/create/-/create-8.1.9.tgz", + "integrity": "sha512-DPnl5lPX4v49eVxEbJnAizrpMdMTBz1qykZrAbBul9rfgk531v8oAt+Pm6O/rpAleRombNM7FJb5rYGzBJatOQ==", "dev": true, "license": "MIT", "dependencies": { "@npmcli/arborist": "7.5.4", "@npmcli/package-json": "5.2.0", "@npmcli/run-script": "8.1.0", - "@nx/devkit": ">=17.1.2 < 20", + "@nx/devkit": ">=17.1.2 < 21", "@octokit/plugin-enterprise-rest": "6.0.1", "@octokit/rest": "19.0.11", "aproba": "2.0.0", @@ -1346,7 +1391,7 @@ "console-control-strings": "^1.1.0", "conventional-changelog-core": "5.0.1", "conventional-recommended-bump": "7.0.1", - "cosmiconfig": "^8.2.0", + "cosmiconfig": "9.0.0", "dedent": "1.5.3", "execa": "5.0.0", "fs-extra": "^11.2.0", @@ -1372,7 +1417,7 @@ "npm-package-arg": "11.0.2", "npm-packlist": "8.0.2", "npm-registry-fetch": "^17.1.0", - "nx": ">=17.1.2 < 20", + "nx": ">=17.1.2 < 21", "p-map": "4.0.0", "p-map-series": "2.1.0", "p-queue": "6.6.2", @@ -1434,51 +1479,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@lerna/create/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@lerna/create/node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@lerna/create/node_modules/glob/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@lerna/create/node_modules/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", @@ -1492,16 +1492,6 @@ "node": "*" } }, - "node_modules/@lerna/create/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/@lerna/create/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -1512,25 +1502,6 @@ "node": ">=8" } }, - "node_modules/@lerna/create/node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@monsterbitar/isomorphic-ws": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@monsterbitar/isomorphic-ws/-/isomorphic-ws-5.3.1.tgz", @@ -1657,22 +1628,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@npmcli/fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", @@ -1793,21 +1748,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "license": "MIT", - "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/name-from-folder": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", @@ -1953,38 +1893,13 @@ "node": "^16.13.0 || >=18.0.0" } }, - "node_modules/@nrwl/devkit": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.6.4.tgz", - "integrity": "sha512-jpr+T5/+21W/fwAMB6zDLZKO+ReYAfOOMIeM8CpeBi/r9nWmjGXaXN9YKwEOYS1fath62Y5ldGK4yZUttv1tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nx/devkit": "19.6.4" - } - }, - "node_modules/@nrwl/tao": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-19.6.4.tgz", - "integrity": "sha512-1J8cD+MFzsmboiGe03VlQZ8gt64k/TaYYPZivnnhOJolPPs75nz1JyJX55uWcKKRy/b7FZNKWIu/6Wp9JDhJrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "nx": "19.6.4", - "tslib": "^2.3.0" - }, - "bin": { - "tao": "index.js" - } - }, "node_modules/@nx/devkit": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.6.4.tgz", - "integrity": "sha512-mBitFwb/gcz8MR7STt7KQG0vf+QcsasDXiSYcf3OWpc6lGE5wn1q5jg6Iabp49Bd/mdHXVLQnP1aV5A+QqFIOQ==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.2.2.tgz", + "integrity": "sha512-uqs0LVvuRRVAfFdn0ewvmr1vsNV9Ztugw36emcLJxskqhBZb10K+vzdTDAZpg5aVE2ISg1BmPidoOyk1tP+Omg==", "dev": true, "license": "MIT", "dependencies": { - "@nrwl/devkit": "19.6.4", "ejs": "^3.1.7", "enquirer": "~2.3.6", "ignore": "^5.0.4", @@ -1995,13 +1910,29 @@ "yargs-parser": "21.1.1" }, "peerDependencies": { - "nx": ">= 17 <= 20" + "nx": ">= 19 <= 21" + } + }, + "node_modules/@nx/devkit/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-19.6.4.tgz", - "integrity": "sha512-kRn2FLvhwJA/TJrNlsCSqqQTrguNZLmiRsiXhvjkfUMbUKwyQfVMgJlvkZ+KoqraUSG+Qyb0FmrGur1I/Mld0Q==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.2.2.tgz", + "integrity": "sha512-gnS5mtbaBAO5TJkl4T68rQaN/79MMWePavw2SOcFyFnIdAriGEZ+ZFDUE0B/xYJSs9CPWLaGHf+n7oqyxaGd9A==", "cpu": [ "arm64" ], @@ -2016,9 +1947,9 @@ } }, "node_modules/@nx/nx-darwin-x64": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-19.6.4.tgz", - "integrity": "sha512-3uABBUhxVk+SdRwpUu30iuBlgRWm3tA/G9seG+wt7oN2R+fOu8zzRCYa+Blvoh1Ef+D9743Ir4rDc9Mhzl2B2g==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.2.2.tgz", + "integrity": "sha512-IctvdQon+K8mlhl06zIq1xTPwf5L4OuS7crzCmK26p5F/lV6iz/UXSPCcgn+bYKOL/q3QCLNR7UasQMjzgCNkQ==", "cpu": [ "x64" ], @@ -2033,9 +1964,9 @@ } }, "node_modules/@nx/nx-freebsd-x64": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-19.6.4.tgz", - "integrity": "sha512-OG83MiEk5L54/vAldmwZJBKEvZaM+DEIDqn2yZLTToBRj5Z9jwKJX3jKP60xbHiaT/hzsb1xPlwhOHJnYd80EQ==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.2.2.tgz", + "integrity": "sha512-4/Blg9Y6LVU8tS8yoa2BEXPHWsorpvCuZRH0gXPh96i6b71o4ORPafyLOHp08o3WjtUZb4jl5TfDryE+8y62ZA==", "cpu": [ "x64" ], @@ -2050,9 +1981,9 @@ } }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-19.6.4.tgz", - "integrity": "sha512-hQ9x4qSKUh9mIVDuD270ULrBnmYfDTjXq7LnIwECw1AuP4LkKzKxULhsbqVnFQ/k3xxyFkwyGTIu2mfDcw16Gw==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.2.2.tgz", + "integrity": "sha512-AVAxbUXi6q+inmp8re3OV7HzH6fbkKnnMKvjDLnkzK8dA2Mv4JFl/gz++rgkYfEsBk20lcB1i3unqNrtOvzS7Q==", "cpu": [ "arm" ], @@ -2067,9 +1998,9 @@ } }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-19.6.4.tgz", - "integrity": "sha512-OFKpAHiaVg3YGFIMBmi/JshciP9buwtOPiHDXcQdzQgE22jyYzKEiFxfcpG0nCT8PlMYAbHPAda15WfWkfVGVQ==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.2.2.tgz", + "integrity": "sha512-h04SLH464Oh/k/1mpAfsMhTVlnc1NJItx4N5DLZb2VuOOY+Tquhrp7HBJLyAhU0Q74JG0LevGFO6wdxliHupmA==", "cpu": [ "arm64" ], @@ -2084,9 +2015,9 @@ } }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-19.6.4.tgz", - "integrity": "sha512-ZIR9u+mN0A7SmNd6vDxmPV1QVTgYPTdfBSM5TEnKl3q2fHw2Nkui81QBxA4d7VopJoJUz/pRHiUV+dlgEEZ6nA==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.2.2.tgz", + "integrity": "sha512-rnRXDLvHHj66rCslD4ShDq6KBOVsQ+X63GWTGKM0pnTIIDje9+ltZCoAByieCUm4BvFfCWMUf9y0mGfZvLVKSw==", "cpu": [ "arm64" ], @@ -2101,9 +2032,9 @@ } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-19.6.4.tgz", - "integrity": "sha512-AUMPvLs9KeCUuWD5DdlpbP3VfVsiD0IlptS2b3ul336rsQ7LwwdvE7jTVO5CixFOsiRZxP72fKJhaEargMn5Aw==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.2.2.tgz", + "integrity": "sha512-K1Z2DVTnyCGl4nolhZ8fvHEixoe1pZOY256LD6D0lGca4Fsi3mHQ7lDU237Pzyc91+cfLva/OAvrivRPeU+DMA==", "cpu": [ "x64" ], @@ -2118,9 +2049,9 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-19.6.4.tgz", - "integrity": "sha512-PU7AaBlrgnJnDxTiV/PNCu0pHUCzaogm6uNcbzCyFJLGn7DoQK9rkqUMPJjb3CnJkAj9XrrhuZwmOdbrhvHAvA==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.2.2.tgz", + "integrity": "sha512-pyWe+d2Y2pJVgPZf27KkDBufhFPq+Xhs3/zAQdJbicMvym7uhw0qMTV+lmoMXgfx52WZzhqTfG8JQcDqHjExJw==", "cpu": [ "x64" ], @@ -2135,9 +2066,9 @@ } }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-19.6.4.tgz", - "integrity": "sha512-6CCmGWwH/J2k+Uxeci48w4QVhtcQ3hRZ5Z2jh26HI8YzH4wqZyA7QPgLBE6sNCPVLoGW5cBgTsfnyEdr+xarQA==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.2.2.tgz", + "integrity": "sha512-zqSoVrV34tx6qhQo/PwD9IMGhzoNSaFQxjTjNCY61sE7iwi5Qt4dDs3Rlh1ZFCBFnrjziymRPY2RryArgeK8Bw==", "cpu": [ "arm64" ], @@ -2152,9 +2083,9 @@ } }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-19.6.4.tgz", - "integrity": "sha512-jTNrlaFaKtbL2mYOcfPAiljtpF5CI7vbHIqYqBFYLUQXOwW9lOHlO+SeQnft6JYZs0FIr1IdHaCfdOw/hpnCiQ==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.2.2.tgz", + "integrity": "sha512-IfQf2axmCuSArhFGaocIDt8ajWDHXoVut5NOQH4eV2q9whP1j/LVB8EehEaolF5UenM7rhL4V25PXPuuBaUq4A==", "cpu": [ "x64" ], @@ -2371,10 +2302,23 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", - "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -2386,9 +2330,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", - "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -2400,9 +2344,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", - "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -2414,9 +2358,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", - "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -2427,10 +2371,38 @@ "darwin" ] }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", - "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -2442,9 +2414,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", - "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -2456,9 +2428,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", - "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -2470,9 +2442,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", - "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -2483,10 +2455,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", - "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -2498,9 +2484,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", - "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -2512,9 +2498,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", - "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -2526,9 +2512,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", - "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -2540,9 +2526,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", - "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -2554,9 +2540,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", - "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -2568,9 +2554,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", - "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -2582,9 +2568,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", - "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -2596,30 +2582,68 @@ ] }, "node_modules/@shikijs/core": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.16.1.tgz", - "integrity": "sha512-aI0hBtw+a6KsJp2jcD4YuQqKpeCbURMZbhHVozDknJpm+KJqeMRkEnfBC8BaKE/5XC+uofPgCLsa/TkTk0Ba0w==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.24.2.tgz", + "integrity": "sha512-BpbNUSKIwbKrRRA+BQj0BEWSw+8kOPKDJevWeSE/xIqGX7K0xrCZQ9kK0nnEQyrzsUoka1l81ZtJ2mGaCA32HQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^9.2.0", - "@types/hast": "^3.0.4" + "@shikijs/engine-javascript": "1.24.2", + "@shikijs/engine-oniguruma": "1.24.2", + "@shikijs/types": "1.24.2", + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.3" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.24.2.tgz", + "integrity": "sha512-EqsmYBJdLEwEiO4H+oExz34a5GhhnVp+jH9Q/XjPjmBPc6TE/x4/gD0X3i0EbkKKNqXYHHJTJUpOLRQNkEzS9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.2", + "@shikijs/vscode-textmate": "^9.3.0", + "oniguruma-to-es": "0.7.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.2.tgz", + "integrity": "sha512-ZN6k//aDNWRJs1uKB12pturKHh7GejKugowOFGAuG7TxDRLod1Bd5JhpOikOiFqPmKjKEPtEA6mRCf7q3ulDyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.24.2", + "@shikijs/vscode-textmate": "^9.3.0" } }, "node_modules/@shikijs/transformers": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.16.1.tgz", - "integrity": "sha512-mfbe4YMov+1eyIBU3F6BtaPmLgDkRQaVse8xsBlKTVAcNF3cbZMRCyUz2N6gJOMKLJiv9T5gapBPbRxrDMuoxA==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.24.2.tgz", + "integrity": "sha512-cIwn8YSwO3bsWKJ+pezcXY1Vq0BVwvuLes1TZSC5+Awi6Tsfqhf3vBahOIqZK1rraMKOti2VEAEF/95oXMig1w==", "dev": true, "license": "MIT", "dependencies": { - "shiki": "1.16.1" + "shiki": "1.24.2" + } + }, + "node_modules/@shikijs/types": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.2.tgz", + "integrity": "sha512-bdeWZiDtajGLG9BudI0AHet0b6e7FbR0EsE4jpGaI0YwHm/XJunI9+3uZnzFtX65gsyJ6ngCIWUfA4NWRPnBkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^9.3.0", + "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-5FinaOp6Vdh/dl4/yaOTh0ZeKch+rYS8DUb38V3GMKYVkdqzxw53lViRKUYkVILRiVQT7dcPC7VvAKOR73zVtQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", + "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==", "dev": true, "license": "MIT" }, @@ -2710,14 +2734,94 @@ "dev": true, "license": "MIT" }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "node_modules/@ts-graphviz/adapter": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ts-graphviz/adapter/-/adapter-2.0.6.tgz", + "integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], "license": "MIT", - "optional": true, + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, "engines": { - "node": ">= 6" + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/ast": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ts-graphviz/ast/-/ast-2.0.6.tgz", + "integrity": "sha512-JbOnw6+Pm+C9jRQlNV+qJG0/VTan4oCeZ0sClm++SjaaMBJ0q86O13i6wbcWKY2x8kKt9GP2hVCgM/p/BXtXWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/common": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@ts-graphviz/common/-/common-2.1.5.tgz", + "integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@ts-graphviz/core": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ts-graphviz/core/-/core-2.0.6.tgz", + "integrity": "sha512-0hvrluFirC0ph3Dn2o1B0O1fI2n7Hre1HlScfmRcO6DDDq/05Vizg5UMI0LfvkJulLuz80RPjUHluh+QfBUBKw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/ast": "^2.0.6", + "@ts-graphviz/common": "^2.1.5" + }, + "engines": { + "node": ">=18" } }, "node_modules/@tufjs/canonical-json": { @@ -2744,22 +2848,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -2770,10 +2858,31 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, "license": "MIT" }, @@ -2787,6 +2896,13 @@ "@types/unist": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -2805,6 +2921,16 @@ "@types/mdurl": "^2" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", @@ -2827,12 +2953,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz", - "integrity": "sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.20.0" } }, "node_modules/@types/normalize-package-data": { @@ -2857,26 +2983,26 @@ "license": "MIT" }, "node_modules/@types/ws": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", - "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", - "integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz", + "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/type-utils": "8.5.0", - "@typescript-eslint/utils": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/type-utils": "8.18.0", + "@typescript-eslint/utils": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2891,25 +3017,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", - "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz", + "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MITClause", "dependencies": { - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "debug": "^4.3.4" }, "engines": { @@ -2920,23 +3042,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", - "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz", + "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0" + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2947,14 +3065,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", - "integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz", + "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/typescript-estree": "8.18.0", + "@typescript-eslint/utils": "8.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2965,16 +3083,15 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", - "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz", + "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==", "dev": true, "license": "MIT", "engines": { @@ -2986,14 +3103,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", - "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz", + "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/visitor-keys": "8.18.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3008,39 +3125,21 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", - "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz", + "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0" + "@typescript-eslint/scope-manager": "8.18.0", + "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3050,18 +3149,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", - "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz", + "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.5.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.18.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3071,89 +3171,269 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz", + "integrity": "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitejs/plugin-vue": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.3.tgz", - "integrity": "sha512-3xbWsKEKXYlmX82aOHufFQVnkbMC/v8fLpWwh6hWOUrK5fbbtBh9Q/WWse27BFgSy2/e2c0fz5Scgya9h2GLhw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, - "node_modules/@vue/compiler-core": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz", - "integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==", + "node_modules/@vitest/coverage-v8": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.38", + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.8", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.8", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz", - "integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz", - "integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.38", - "@vue/compiler-dom": "3.4.38", - "@vue/compiler-ssr": "3.4.38", - "@vue/shared": "3.4.38", + "@babel/parser": "^7.25.3", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.40", + "magic-string": "^0.30.11", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz", - "integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/devtools-api": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.3.9.tgz", - "integrity": "sha512-D+GTYtFg68bqSu66EugQUydsOqaDlPLNmYw5oYk8k81uBu9/bVTUrqlAJrAA9Am7MXhKz2gWdDkopY6sOBf/Bg==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.6.8.tgz", + "integrity": "sha512-ma6dY/sZR36zALVsV1W7eC57c6IJPXsy8SNgZn1PLVWU4z4dPn5TIBmnF4stmdJ4sQcixqKaQ8pwjbMPzEZwiA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.3.9" + "@vue/devtools-kit": "^7.6.8" } }, "node_modules/@vue/devtools-kit": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.9.tgz", - "integrity": "sha512-Gr17nA+DaQzqyhNx1DUJr1CJRzTRfbIuuC80ZgU8MD/qNO302tv9la+ROi+Uaw+ULVwU9T71GnwLy4n8m9Lspg==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.8.tgz", + "integrity": "sha512-JhJ8M3sPU+v0P2iZBF2DkdmR9L0dnT5RXJabJqX6o8KtFs3tebdvfoXV2Dm3BFuqeECuMJIfF1aCzSt+WQ4wrw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^7.3.9", - "birpc": "^0.2.17", + "@vue/devtools-shared": "^7.6.8", + "birpc": "^0.2.19", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", @@ -3162,9 +3442,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.9.tgz", - "integrity": "sha512-CdfMRZKXyI8vw+hqOcQIiLihB6Hbbi7WNZGp7LsuH1Qe4aYAFmTaKjSciRZ301oTnwmU/knC/s5OGuV6UNiNoA==", + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.8.tgz", + "integrity": "sha512-9MBPO5Z3X1nYGFqTJyohl6Gmf/J7UNN1oicHdyzBVZP4jnhZ4c20MgtaHDIzWmHDHCMYVS5bwKxT3jxh7gOOKA==", "dev": true, "license": "MIT", "dependencies": { @@ -3172,65 +3452,70 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz", - "integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.4.38" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz", - "integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz", - "integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.38", - "@vue/runtime-core": "3.4.38", - "@vue/shared": "3.4.38", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz", - "integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.38", - "@vue/shared": "3.4.38" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.4.38" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz", - "integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", + "dev": true, "license": "MIT" }, "node_modules/@vueuse/core": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.0.3.tgz", - "integrity": "sha512-RENlh64+SYA9XMExmmH1a3TPqeIuJBNNB/63GT35MZI+zpru3oMRUA6cEFr9HmGqEgUisurwGwnIieF6qu3aXw==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.3.0.tgz", + "integrity": "sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==", "dev": true, "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "11.0.3", - "@vueuse/shared": "11.0.3", + "@vueuse/metadata": "11.3.0", + "@vueuse/shared": "11.3.0", "vue-demi": ">=0.14.10" }, "funding": { @@ -3265,14 +3550,14 @@ } }, "node_modules/@vueuse/integrations": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.0.3.tgz", - "integrity": "sha512-w6CDisaxs19S5Fd+NPPLFaA3GoX5gxuxrbTTBu0EYap7oH13w75L6C/+7e9mcoF9akhcR6GyYajwVMQEjdapJg==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-11.3.0.tgz", + "integrity": "sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw==", "dev": true, "license": "MIT", "dependencies": { - "@vueuse/core": "11.0.3", - "@vueuse/shared": "11.0.3", + "@vueuse/core": "11.3.0", + "@vueuse/shared": "11.3.0", "vue-demi": ">=0.14.10" }, "funding": { @@ -3359,9 +3644,9 @@ } }, "node_modules/@vueuse/metadata": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.0.3.tgz", - "integrity": "sha512-+FtbO4SD5WpsOcQTcC0hAhNlOid6QNLzqedtquTtQ+CRNBoAt9GuV07c6KNHK1wCmlq8DFPwgiLF2rXwgSHX5Q==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.3.0.tgz", + "integrity": "sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==", "dev": true, "license": "MIT", "funding": { @@ -3369,9 +3654,9 @@ } }, "node_modules/@vueuse/shared": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.0.3.tgz", - "integrity": "sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==", + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.3.0.tgz", + "integrity": "sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==", "dev": true, "license": "MIT", "dependencies": { @@ -3408,16 +3693,20 @@ } } }, - "node_modules/@xo/primitives": { + "node_modules/@xocash/docs": { + "resolved": "packages/docs", + "link": true + }, + "node_modules/@xocash/json-payment-protocol-v2": { + "resolved": "packages/json-payment-protocol-v2", + "link": true + }, + "node_modules/@xocash/primitives": { "resolved": "packages/primitives", "link": true }, - "node_modules/@xo/vue": { - "resolved": "packages/vue", - "link": true - }, - "node_modules/@xo/wallet": { - "resolved": "packages/wallet", + "node_modules/@xocash/stack": { + "resolved": "packages/stack", "link": true }, "node_modules/@yarnpkg/lockfile": { @@ -3428,9 +3717,9 @@ "license": "BSD-2-Clause" }, "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3438,7 +3727,7 @@ "tslib": "^2.4.0" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" } }, "node_modules/@yarnpkg/parsers/node_modules/argparse": { @@ -3496,9 +3785,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", "bin": { @@ -3526,36 +3815,20 @@ "license": "MIT" }, "node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, "engines": { "node": ">= 14" } }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "license": "MIT", - "optional": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -3583,70 +3856,28 @@ } }, "node_modules/algoliasearch": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.24.0.tgz", - "integrity": "sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.17.1.tgz", + "integrity": "sha512-3CcbT5yTWJDIcBe9ZHgsPi184SkT1kyZi3GWlQU5EFgvq1V73X2sqHRkPCQMe0RA/uvZbB+1sFeAk73eWygeLg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/cache-browser-local-storage": "4.24.0", - "@algolia/cache-common": "4.24.0", - "@algolia/cache-in-memory": "4.24.0", - "@algolia/client-account": "4.24.0", - "@algolia/client-analytics": "4.24.0", - "@algolia/client-common": "4.24.0", - "@algolia/client-personalization": "4.24.0", - "@algolia/client-search": "4.24.0", - "@algolia/logger-common": "4.24.0", - "@algolia/logger-console": "4.24.0", - "@algolia/recommend": "4.24.0", - "@algolia/requester-browser-xhr": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/requester-node-http": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/client-common": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.24.0.tgz", - "integrity": "sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/client-search": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.24.0.tgz", - "integrity": "sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "4.24.0", - "@algolia/requester-common": "4.24.0", - "@algolia/transporter": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/requester-browser-xhr": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.24.0.tgz", - "integrity": "sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0" - } - }, - "node_modules/algoliasearch/node_modules/@algolia/requester-node-http": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.24.0.tgz", - "integrity": "sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/requester-common": "4.24.0" + "@algolia/client-abtesting": "5.17.1", + "@algolia/client-analytics": "5.17.1", + "@algolia/client-common": "5.17.1", + "@algolia/client-insights": "5.17.1", + "@algolia/client-personalization": "5.17.1", + "@algolia/client-query-suggestions": "5.17.1", + "@algolia/client-search": "5.17.1", + "@algolia/ingestion": "1.17.1", + "@algolia/monitoring": "1.17.1", + "@algolia/recommend": "5.17.1", + "@algolia/requester-browser-xhr": "5.17.1", + "@algolia/requester-fetch": "5.17.1", + "@algolia/requester-node-http": "5.17.1" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/ansi-colors": { @@ -3675,24 +3906,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3714,28 +3932,27 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-module-path": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", + "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3780,6 +3997,26 @@ "node": ">=0.10.0" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-module-types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.0.tgz", + "integrity": "sha512-LFRg7178Fw5R4FAEwZxVqiRI8IxSM+Ay2UBrHoCerXNme+kMMMfz7T3xDGV/c2fer87hcrtgJGsnSOfUrPK6ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3788,9 +4025,9 @@ "license": "MIT" }, "node_modules/async-mutex": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", - "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz", + "integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==", "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -3804,9 +4041,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "dev": true, "license": "MIT", "dependencies": { @@ -3819,13 +4056,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, "funding": [ { "type": "github", @@ -3865,19 +4103,10 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, "node_modules/birpc": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.17.tgz", - "integrity": "sha512-+hkTxhot+dWsLpp3gia5AkVHIsKlZybNT5gIYiDlNzJrmYPcTM9k5/w2uaj3IPpd7LlEYpmCj4Jj1nC41VhDFg==", + "version": "0.2.19", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", + "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", "dev": true, "license": "MIT", "funding": { @@ -3888,6 +4117,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, "license": "MIT", "dependencies": { "buffer": "^5.5.0", @@ -3922,6 +4152,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, "funding": [ { "type": "github", @@ -3959,6 +4190,16 @@ "node": ">=12.17" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cacache": { "version": "18.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", @@ -4021,6 +4262,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", + "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4038,6 +4307,28 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -4045,10 +4336,21 @@ "dev": true, "license": "MIT" }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -4074,7 +4376,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4221,7 +4523,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "color-support": "bin.js" @@ -4254,6 +4556,27 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/common-ancestor-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", @@ -4261,6 +4584,13 @@ "dev": true, "license": "ISC" }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -4276,7 +4606,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -4299,7 +4629,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/conventional-changelog-angular": { @@ -4491,16 +4821,16 @@ "license": "MIT" }, "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", "dev": true, "license": "MIT", "dependencies": { + "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" + "parse-json": "^5.2.0" }, "engines": { "node": ">=14" @@ -4518,9 +4848,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -4549,6 +4879,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, "node_modules/dargs": { @@ -4572,12 +4903,12 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4625,21 +4956,6 @@ "node": ">=0.10.0" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -4655,10 +4971,21 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4.0.0" @@ -4704,12 +5031,34 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "node_modules/dependency-tree": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.0.1.tgz", + "integrity": "sha512-eCt7HSKIC9NxgIykG2DRq3Aewn9UhVS14MB3rEn6l/AsEI1FBg6ZGSlCU0SZ6Tjm2kkhj6/8c2pViinuyKELhg==", + "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "commander": "^12.0.0", + "filing-cabinet": "^5.0.1", + "precinct": "^12.0.2", + "typescript": "^5.4.5" + }, + "bin": { + "dependency-tree": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/dependency-tree/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/deprecation": { "version": "2.3.1", @@ -4718,6 +5067,16 @@ "dev": true, "license": "ISC" }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-indent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", @@ -4728,13 +5087,220 @@ "node": ">=4" } }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", + "node_modules/detective-amd": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.0.tgz", + "integrity": "sha512-NTqfYfwNsW7AQltKSEaWR66hGkTeD52Kz3eRQ+nfkA9ZFZt3iifRCWh+yZ/m6t3H42JFwVFTrml/D64R2PAIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "escodegen": "^2.1.0", + "get-amd-module-type": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "bin": { + "detective-amd": "bin/cli.js" + }, "engines": { - "node": ">=8" + "node": ">=18" + } + }, + "node_modules/detective-cjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.0.0.tgz", + "integrity": "sha512-R55jTS6Kkmy6ukdrbzY4x+I7KkXiuDPpFzUViFV/tm2PBGtTCjkh9ZmTuJc1SaziMHJOe636dtiZLEuzBL9drg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-es6": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.0.tgz", + "integrity": "sha512-NGTnzjvgeMW1khUSEXCzPDoraLenWbUjCFjwxReH+Ir+P6LGjYtaBbAvITWn2H0VSC+eM7/9LFOTAkrta6hNYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-postcss": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.0.tgz", + "integrity": "sha512-pSXA6dyqmBPBuERpoOKKTUUjQCZwZPLRbd1VdsTbt6W+m/+6ROl4BbE87yQBUtLoK7yX8pvXHdKyM/xNIW9F7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-url": "^1.2.4", + "postcss-values-parser": "^6.0.2" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/detective-sass": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.0.tgz", + "integrity": "sha512-h5GCfFMkPm4ZUUfGHVPKNHKT8jV7cSmgK+s4dgQH4/dIUNh9/huR1fjEQrblOQNDalSU7k7g+tiW9LJ+nVEUhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-scss": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.0.tgz", + "integrity": "sha512-Y64HyMqntdsCh1qAH7ci95dk0nnpA29g319w/5d/oYcHolcGUVJbIhOirOFjfN1KnMAXAFm5FIkZ4l2EKFGgxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "gonzales-pe": "^4.3.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-stylus": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.0.tgz", + "integrity": "sha512-KMHOsPY6aq3196WteVhkY5FF+6Nnc/r7q741E+Gq+Ax9mhE2iwj8Hlw8pl+749hPDRDBHZ2WlgOjP+twIG61vQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detective-typescript": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-13.0.0.tgz", + "integrity": "sha512-tcMYfiFWoUejSbvSblw90NDt76/4mNftYCX0SMnVRYzSXv8Fvo06hi4JOPdNvVNxRtCAKg3MJ3cBJh+ygEMH+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "^7.6.0", + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + }, + "peerDependencies": { + "typescript": "^5.4.4" + } + }, + "node_modules/detective-typescript/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/detective-typescript/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/detective-typescript/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/detective-vue2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.1.0.tgz", + "integrity": "sha512-IHQVhwk7dKaJ+GHBsL27mS9NRO1/vLZJPSODqtJgKquij0/UL8NvrbXbADbYeTkwyh1ReW/v9u9IRyEO5dvGZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dependents/detective-less": "^5.0.0", + "@vue/compiler-sfc": "^3.5.12", + "detective-es6": "^5.0.0", + "detective-sass": "^6.0.0", + "detective-scss": "^5.0.0", + "detective-stylus": "^5.0.0", + "detective-typescript": "^13.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "typescript": "^5.4.4" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/diff-sequences": { @@ -4774,9 +5340,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4787,13 +5353,13 @@ } }, "node_modules/dotenv-expand": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", - "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "dotenv": "^16.4.4" + "dotenv": "^16.4.5" }, "engines": { "node": ">=12" @@ -4836,13 +5402,21 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "devOptional": true, + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, "license": "MIT" }, "node_modules/encoding": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4853,6 +5427,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -4866,11 +5441,26 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "license": "MIT", "dependencies": { "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -4888,6 +5478,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -4900,7 +5491,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4923,7 +5514,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/error-ex": { @@ -4936,6 +5527,13 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -4998,30 +5596,55 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", - "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.10.0", - "@eslint/plugin-kit": "^0.1.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5031,14 +5654,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -5071,10 +5691,41 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5113,9 +5764,9 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5139,15 +5790,15 @@ } }, "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5157,9 +5808,9 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -5223,6 +5874,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, "license": "MIT" }, "node_modules/esutils": { @@ -5266,13 +5918,14 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "license": "(MIT OR WTFPL)", + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": ">=12.0.0" } }, "node_modules/exponential-backoff": { @@ -5317,6 +5970,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -5410,12 +6070,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "license": "MIT" - }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -5439,6 +6093,42 @@ "node": ">=10" } }, + "node_modules/filing-cabinet": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.0.2.tgz", + "integrity": "sha512-RZlFj8lzyu6jqtFBeXNqUjjNG6xm+gwXue3T70pRxw1W40kJwlgq0PSWAmh0nAnn5DHuBIecLXk9+1VKS9ICXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-module-path": "^2.2.0", + "commander": "^12.0.0", + "enhanced-resolve": "^5.16.0", + "module-definition": "^6.0.0", + "module-lookup-amd": "^9.0.1", + "resolve": "^1.22.8", + "resolve-dependency-path": "^4.0.0", + "sass-lookup": "^6.0.1", + "stylus-lookup": "^6.0.0", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.4.4" + }, + "bin": { + "filing-cabinet": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/filing-cabinet/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5494,16 +6184,16 @@ } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true, "license": "ISC" }, "node_modules/focus-trap": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", - "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.2.tgz", + "integrity": "sha512-9FhUxK1hVju2+AiQIDJ5Dd//9R2n2RAfJ0qfhF4IHGHgcoEUTMpbTeG/zbEuwaiYXfuAH6XE0/aCyxDdRM+W5w==", "dev": true, "license": "MIT", "dependencies": { @@ -5511,9 +6201,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "dev": true, "funding": [ { @@ -5562,9 +6252,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "license": "MIT", "dependencies": { @@ -5621,6 +6311,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, "license": "MIT" }, "node_modules/fs-extra": { @@ -5655,7 +6346,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -5683,25 +6374,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, + "node_modules/get-amd-module-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.0.tgz", + "integrity": "sha512-hFM7oivtlgJ3d6XWD6G47l8Wyh/C6vFw5G24Kk1Tbq85yh5gcM8Fne5/lFhiuxB+RT6+SI7I1ThB9lG4FBh3jw==", + "dev": true, + "license": "MIT", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=18" } }, "node_modules/get-caller-file": { @@ -5714,6 +6398,13 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, "node_modules/get-pkg-repo": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", @@ -5934,12 +6625,6 @@ "ini": "^1.3.2" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "license": "MIT" - }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -5974,22 +6659,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -6024,11 +6693,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gonzales-pe": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", + "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "gonzales": "bin/gonzales.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -6084,7 +6769,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/hasown": { @@ -6100,6 +6785,44 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", + "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", @@ -6120,11 +6843,29 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", - "devOptional": true, + "dev": true, "license": "BSD-2-Clause" }, "node_modules/http-proxy-agent": { @@ -6142,13 +6883,13 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", - "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -6165,16 +6906,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6188,16 +6919,17 @@ "node": ">=0.10.0" } }, - "node_modules/idb-keyval": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", - "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", - "license": "Apache-2.0" + "node_modules/idb": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.1.tgz", + "integrity": "sha512-EkBCzUZSdhJV8PxMSbeEV//xguVKZu9hZZulM+2gHXI0t2hGVU3eYE6/XnH77DS6FM2FY8wl17aDcu9vXpvLWQ==", + "license": "ISC" }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, "funding": [ { "type": "github", @@ -6278,7 +7010,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -6288,26 +7020,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "license": "ISC", - "optional": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, "license": "ISC", - "optional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6317,12 +7042,14 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, "license": "ISC" }, "node_modules/init-package-json": { @@ -6375,7 +7102,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "jsbn": "1.1.0", @@ -6406,9 +7133,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "dev": true, "license": "MIT", "dependencies": { @@ -6451,7 +7178,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6484,7 +7211,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/is-number": { @@ -6507,16 +7234,6 @@ "node": ">=8" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -6537,6 +7254,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-ssh": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz", @@ -6583,6 +7310,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-url-superb": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", + "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-what": { "version": "4.1.16", "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", @@ -6620,7 +7367,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/isobject": { @@ -6633,6 +7380,60 @@ "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -6742,7 +7543,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/json-buffer": { @@ -6895,17 +7696,17 @@ } }, "node_modules/lerna": { - "version": "8.1.8", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.1.8.tgz", - "integrity": "sha512-Rmo5ShMx73xM2CUcRixjmpZIXB7ZFlWEul1YvJyx/rH4onAwDHtUGD7Rx4NZYL8QSRiQHroglM2Oyq+WqA4BYg==", + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-8.1.9.tgz", + "integrity": "sha512-ZRFlRUBB2obm+GkbTR7EbgTMuAdni6iwtTQTMy7LIrQ4UInG44LyfRepljtgUxh4HA0ltzsvWfPkd5J1DKGCeQ==", "dev": true, "license": "MIT", "dependencies": { - "@lerna/create": "8.1.8", + "@lerna/create": "8.1.9", "@npmcli/arborist": "7.5.4", "@npmcli/package-json": "5.2.0", "@npmcli/run-script": "8.1.0", - "@nx/devkit": ">=17.1.2 < 20", + "@nx/devkit": ">=17.1.2 < 21", "@octokit/plugin-enterprise-rest": "6.0.1", "@octokit/rest": "19.0.11", "aproba": "2.0.0", @@ -6919,7 +7720,7 @@ "conventional-changelog-angular": "7.0.0", "conventional-changelog-core": "5.0.1", "conventional-recommended-bump": "7.0.1", - "cosmiconfig": "^8.2.0", + "cosmiconfig": "9.0.0", "dedent": "1.5.3", "envinfo": "7.13.0", "execa": "5.0.0", @@ -6950,7 +7751,7 @@ "npm-package-arg": "11.0.2", "npm-packlist": "8.0.2", "npm-registry-fetch": "^17.1.0", - "nx": ">=17.1.2 < 20", + "nx": ">=17.1.2 < 21", "p-map": "4.0.0", "p-map-series": "2.1.0", "p-pipe": "3.1.0", @@ -7018,51 +7819,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/lerna/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/lerna/node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/lerna/node_modules/glob/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/lerna/node_modules/minimatch": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz", @@ -7076,16 +7832,6 @@ "node": "*" } }, - "node_modules/lerna/node_modules/minipass": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, "node_modules/lerna/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -7096,25 +7842,6 @@ "node": ">=8" } }, - "node_modules/lerna/node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7164,9 +7891,9 @@ } }, "node_modules/libnpmpublish/node_modules/ci-info": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", - "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "dev": true, "funding": [ { @@ -7180,9 +7907,9 @@ } }, "node_modules/lines-and-columns": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", - "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", "dev": true, "license": "MIT", "engines": { @@ -7280,9 +8007,16 @@ } }, "node_modules/lossless-json": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-2.0.11.tgz", - "integrity": "sha512-BP0vn+NGYvzDielvBZaFain/wgeJ1hTvURCqtKvhr1SCPePdaaTanmmcplrHfEJSJOUql7hk4FHwToNJjWRY3g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.0.2.tgz", + "integrity": "sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA==", + "license": "MIT" + }, + "node_modules/loupe": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", + "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", + "dev": true, "license": "MIT" }, "node_modules/lru-cache": { @@ -7299,15 +8033,67 @@ "dev": true, "license": "MIT" }, + "node_modules/madge": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/madge/-/madge-8.0.0.tgz", + "integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^7.2.0", + "commondir": "^1.0.1", + "debug": "^4.3.4", + "dependency-tree": "^11.0.0", + "ora": "^5.4.1", + "pluralize": "^8.0.0", + "pretty-ms": "^7.0.1", + "rc": "^1.2.8", + "stream-to-array": "^2.3.0", + "ts-graphviz": "^2.1.2", + "walkdir": "^0.4.1" + }, + "bin": { + "madge": "bin/cli.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/pahen" + }, + "peerDependencies": { + "typescript": "^5.4.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/magic-string": { - "version": "0.30.11", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", - "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -7386,6 +8172,28 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -7641,6 +8449,100 @@ "node": ">= 8" } }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -7688,18 +8590,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -7711,9 +8601,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", "dependencies": { @@ -7730,6 +8620,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7795,7 +8686,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -7808,7 +8699,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -7821,7 +8712,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -7834,7 +8725,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -7847,7 +8738,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -7860,7 +8751,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -7870,9 +8761,9 @@ } }, "node_modules/minisearch": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.0.tgz", - "integrity": "sha512-tv7c/uefWdEhcu6hvrfTihflgeEi2tN6VV7HJnCjK6VxM75QQJh4t9FwJCsA2EsRS8LCnu3W87CuGPWMocOLCA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.1.1.tgz", + "integrity": "sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==", "dev": true, "license": "MIT" }, @@ -7880,6 +8771,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^3.0.0", @@ -7893,6 +8785,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -7912,6 +8805,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -7920,12 +8814,6 @@ "node": ">=10" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/modify-values": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", @@ -7936,10 +8824,102 @@ "node": ">=0.10.0" } }, + "node_modules/module-definition": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.0.tgz", + "integrity": "sha512-sEGP5nKEXU7fGSZUML/coJbrO+yQtxcppDAYWRE9ovWsTbFoUHB2qDUx564WUzDaBHXsD46JBbIK5WVTwCyu3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-module-types": "^6.0.0", + "node-source-walk": "^7.0.0" + }, + "bin": { + "module-definition": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.0.2.tgz", + "integrity": "sha512-p7PzSVEWiW9fHRX9oM+V4aV5B2nCVddVNv4DZ/JB6t9GsXY4E+ZVhPpnwUX7bbJyGeeVZqhS8q/JZ/H77IqPFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.1.0", + "glob": "^7.2.3", + "requirejs": "^2.3.7", + "requirejs-config-file": "^4.0.0" + }, + "bin": { + "lookup-amd": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/module-lookup-amd/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/module-lookup-amd/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/module-lookup-amd/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/multimatch": { @@ -8004,9 +8984,10 @@ "license": "ISC" }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "dev": true, "funding": [ { "type": "github", @@ -8021,12 +9002,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8035,10 +9010,10 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "devOptional": true, + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -8051,24 +9026,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-abi": { - "version": "3.67.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.67.0.tgz", - "integrity": "sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -8091,9 +9048,9 @@ } }, "node_modules/node-gyp": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", - "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", + "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8148,6 +9105,19 @@ "dev": true, "license": "MIT" }, + "node_modules/node-source-walk": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.0.tgz", + "integrity": "sha512-1uiY543L+N7Og4yswvlm5NCKgPKDEXd9AUR9Jh3gen6oOeBsesr6LqhXom1er3eRzSUcVRWXzhv8tSNrIfGHKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.24.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", @@ -8293,35 +9263,17 @@ "node": ">=8" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "optional": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/nx": { - "version": "19.6.4", - "resolved": "https://registry.npmjs.org/nx/-/nx-19.6.4.tgz", - "integrity": "sha512-yudYlBd3cMchRd8c0ZNan1SfT19AYK2zYOp77XVqVVwgqb6rJVxz6StFGFluUQ7Q7uSpkeT/8PsjwPVLt85EoQ==", + "version": "20.2.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-20.2.2.tgz", + "integrity": "sha512-wHgC/NQ82Q3LOeUZXPI2j/JhpZwb7JjRc0uDn3kQU+lN/ulySCJHTHCf4CIglW4NjZeN1WZZ7YMeddtFWETGGA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", - "@nrwl/tao": "19.6.4", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.0-rc.46", + "@yarnpkg/parsers": "3.0.2", "@zkochan/js-yaml": "0.0.7", "axios": "^1.7.4", "chalk": "^4.1.0", @@ -8334,11 +9286,10 @@ "figures": "3.2.0", "flat": "^5.0.2", "front-matter": "^4.0.2", - "fs-extra": "^11.1.0", "ignore": "^5.0.4", "jest-diff": "^29.4.1", "jsonc-parser": "3.2.0", - "lines-and-columns": "~2.0.3", + "lines-and-columns": "2.0.3", "minimatch": "9.0.3", "node-machine-id": "1.1.12", "npm-run-path": "^4.0.1", @@ -8346,11 +9297,11 @@ "ora": "5.3.0", "semver": "^7.5.3", "string-width": "^4.2.3", - "strong-log-transformer": "^2.1.0", "tar-stream": "~2.2.0", "tmp": "~0.2.1", "tsconfig-paths": "^4.1.2", "tslib": "^2.3.0", + "yaml": "^2.6.0", "yargs": "^17.6.2", "yargs-parser": "21.1.1" }, @@ -8359,16 +9310,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "19.6.4", - "@nx/nx-darwin-x64": "19.6.4", - "@nx/nx-freebsd-x64": "19.6.4", - "@nx/nx-linux-arm-gnueabihf": "19.6.4", - "@nx/nx-linux-arm64-gnu": "19.6.4", - "@nx/nx-linux-arm64-musl": "19.6.4", - "@nx/nx-linux-x64-gnu": "19.6.4", - "@nx/nx-linux-x64-musl": "19.6.4", - "@nx/nx-win32-arm64-msvc": "19.6.4", - "@nx/nx-win32-x64-msvc": "19.6.4" + "@nx/nx-darwin-arm64": "20.2.2", + "@nx/nx-darwin-x64": "20.2.2", + "@nx/nx-freebsd-x64": "20.2.2", + "@nx/nx-linux-arm-gnueabihf": "20.2.2", + "@nx/nx-linux-arm64-gnu": "20.2.2", + "@nx/nx-linux-arm64-musl": "20.2.2", + "@nx/nx-linux-x64-gnu": "20.2.2", + "@nx/nx-linux-x64-musl": "20.2.2", + "@nx/nx-win32-arm64-msvc": "20.2.2", + "@nx/nx-win32-x64-msvc": "20.2.2" }, "peerDependencies": { "@swc-node/register": "^1.8.0", @@ -8383,6 +9334,22 @@ } } }, + "node_modules/nx/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/nx/node_modules/ora": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz", @@ -8410,6 +9377,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -8431,6 +9399,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oniguruma-to-es": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-0.7.0.tgz", + "integrity": "sha512-HRaRh09cE0gRS3+wi2zxekB+I5L8C/gN60S+vb11eADHUaB/q4u8wGGOX3GvwvitG8ixaeycZfeoyruKQzUgNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.0.2", + "regex-recursion": "^4.3.0" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -8547,7 +9527,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "aggregate-error": "^3.0.0" @@ -8649,9 +9629,9 @@ } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true, "license": "BlueOak-1.0.0" }, @@ -8748,6 +9728,16 @@ "dev": true, "license": "MIT" }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/parse-path": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz", @@ -8782,8 +9772,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.10.0" } @@ -8832,6 +9822,23 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -8840,9 +9847,10 @@ "license": "MIT" }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -8940,10 +9948,21 @@ "node": ">=8" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/postcss": { - "version": "8.4.43", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.43.tgz", - "integrity": "sha512-gJAQVYbh5R3gYm33FijzCZj7CHyQ3hWMgJMprLUlIYqCwTeZhBQ19wp0e9mA25BUbEvY5+EXuuaAjqQsrBxQBQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -8961,8 +9980,8 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.1", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -8982,10 +10001,28 @@ "node": ">=4" } }, + "node_modules/postcss-values-parser": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", + "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "color-name": "^1.1.4", + "is-url-superb": "^4.0.0", + "quote-unquote": "^1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.2.9" + } + }, "node_modules/preact": { - "version": "10.23.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.23.2.tgz", - "integrity": "sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==", + "version": "10.25.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.25.2.tgz", + "integrity": "sha512-GEts1EH3oMnqdOIeXhlbBSddZ9nrINd070WBOiPO2ous1orrKGUM4SMDbwyjSWD1iMS2dBvaDjAa5qUhz3TXqw==", "dev": true, "license": "MIT", "funding": { @@ -8993,30 +10030,44 @@ "url": "https://opencollective.com/preact" } }, - "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "node_modules/precinct": { + "version": "12.1.2", + "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.1.2.tgz", + "integrity": "sha512-x2qVN3oSOp3D05ihCd8XdkIPuEQsyte7PSxzLqiRgktu79S5Dr1I75/S+zAup8/0cwjoiJTQztE9h0/sWp9bJQ==", + "dev": true, "license": "MIT", "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" + "@dependents/detective-less": "^5.0.0", + "commander": "^12.1.0", + "detective-amd": "^6.0.0", + "detective-cjs": "^6.0.0", + "detective-es6": "^5.0.0", + "detective-postcss": "^7.0.0", + "detective-sass": "^6.0.0", + "detective-scss": "^5.0.0", + "detective-stylus": "^5.0.0", + "detective-typescript": "^13.0.0", + "detective-vue2": "^2.0.3", + "module-definition": "^6.0.0", + "node-source-walk": "^7.0.0", + "postcss": "^8.4.40", + "typescript": "^5.5.4" }, "bin": { - "prebuild-install": "bin.js" + "precinct": "bin/cli.js" }, "engines": { - "node": ">=10" + "node": ">=18" + } + }, + "node_modules/precinct/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/prelude-ls": { @@ -9030,21 +10081,34 @@ } }, "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { - "prettier": "bin-prettier.js" + "prettier": "bin/prettier.cjs" }, "engines": { - "node": ">=10.13.0" + "node": ">=14" }, "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -9073,6 +10137,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/proc-log": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", @@ -9111,9 +10191,9 @@ } }, "node_modules/promise-call-limit": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.1.tgz", - "integrity": "sha512-utl+0x8gIDasV5X+PI5qWEPqH6fJS0pFtQ/4gZ95xfEFb/89dmh+/b895TbFDBLiafBvxD/PGTKfvxl4kH/pQg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", "dev": true, "license": "ISC", "funding": { @@ -9124,14 +10204,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "err-code": "^2.0.2", @@ -9154,6 +10234,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/protocols": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", @@ -9168,16 +10259,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -9229,10 +10310,18 @@ "node": ">=8" } }, + "node_modules/quote-unquote": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", + "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true, + "license": "MIT" + }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", @@ -9248,6 +10337,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9506,6 +10596,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -9530,6 +10621,33 @@ "node": ">=8" } }, + "node_modules/regex": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.0.2.tgz", + "integrity": "sha512-/pczGbKIQgfTMRV0XjABvc5RzLqQmwqxLHdQao2RTXPk+pmTXB2P0IaUHYdYyk412YLwUIkaeMd5T+RzVgTqnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-4.3.0.tgz", + "integrity": "sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9540,14 +10658,42 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "node_modules/requirejs": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.7.tgz", + "integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==", + "dev": true, + "license": "MIT", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/requirejs-config-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", + "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "esprima": "^4.0.0", + "stringify-object": "^3.2.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -9581,6 +10727,16 @@ "node": ">=8" } }, + "node_modules/resolve-dependency-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.0.tgz", + "integrity": "sha512-hlY1SybBGm5aYN3PC4rp15MzsJLM1w+MEA/4KU3UBPfz4S0lL3FL6mgv7JgaA8a+ZTeEQAiF1a1BuN2nkqiIlg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -9609,7 +10765,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -9634,66 +10790,106 @@ "license": "MIT" }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", + "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", + "dev": true, "license": "ISC", - "optional": true, "dependencies": { - "glob": "^7.1.3" + "glob": "^9.2.0" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, "license": "ISC", - "optional": true, "dependencies": { "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", + "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, "license": "ISC", - "optional": true, "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", + "fsevents": "~2.3.2" } }, "node_modules/run-async": { @@ -9744,6 +10940,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, "funding": [ { "type": "github", @@ -9764,13 +10961,39 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "devOptional": true, + "dev": true, "license": "MIT" }, + "node_modules/sass-lookup": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.0.1.tgz", + "integrity": "sha512-nl9Wxbj9RjEJA5SSV0hSDoU2zYGtE+ANaDS4OFUR7nYrquvBFvPKZZtQHe3lvnxCcylEDV00KUijjdMTUElcVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0" + }, + "bin": { + "sass-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/sass-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/search-insights": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.0.tgz", - "integrity": "sha512-AskayU3QNsXQzSL6v4LTYST7NNfs2HWyHHB+sdORP9chsytAhro5XRfToAMI/LAVYgNbzowVZTMfBRodgbUHKg==", + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "dev": true, "license": "MIT", "peer": true @@ -9779,6 +11002,7 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9791,7 +11015,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/shallow-clone": { @@ -9831,22 +11055,32 @@ } }, "node_modules/shiki": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.16.1.tgz", - "integrity": "sha512-tCJIMaxDVB1mEIJ5TvfZU7kCPB5eo9fli5+21Olc/bmyv+w8kye3JOp+LZRmGkAyT71hrkefQhTiY+o9mBikRQ==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.24.2.tgz", + "integrity": "sha512-TR1fi6mkRrzW+SKT5G6uKuc32Dj2EEa7Kj0k8kGqiBINb+C1TiflVOiT9ta6GqOJtC4fraxO5SLUaKBcSY38Fg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/core": "1.16.1", - "@shikijs/vscode-textmate": "^9.2.0", + "@shikijs/core": "1.24.2", + "@shikijs/engine-javascript": "1.24.2", + "@shikijs/engine-oniguruma": "1.24.2", + "@shikijs/types": "1.24.2", + "@shikijs/vscode-textmate": "^9.3.0", "@types/hast": "^3.0.4" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/sigstore": { @@ -9867,51 +11101,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -9926,7 +11115,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -9937,7 +11126,7 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ip-address": "^9.0.5", @@ -9949,13 +11138,13 @@ } }, "node_modules/socks-proxy-agent": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", - "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "^7.1.1", + "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, @@ -9987,14 +11176,26 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -10068,362 +11269,9 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause" }, - "node_modules/sqlite": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz", - "integrity": "sha512-oBkezXa2hnkfuJwUo44Hl9hS3er+YFtueifoajrgidvqsJRQFpc5fKoAkAor1O5ZnLoa28GBScfHXs8j0K358Q==", - "license": "MIT" - }, - "node_modules/sqlite3": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", - "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^7.0.0", - "prebuild-install": "^7.1.1", - "tar": "^6.1.11" - }, - "optionalDependencies": { - "node-gyp": "8.x" - }, - "peerDependencies": { - "node-gyp": "8.x" - }, - "peerDependenciesMeta": { - "node-gyp": { - "optional": true - } - } - }, - "node_modules/sqlite3/node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" - } - }, - "node_modules/sqlite3/node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC", - "optional": true - }, - "node_modules/sqlite3/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/sqlite3/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/sqlite3/node_modules/cacache": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", - "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "@npmcli/fs": "^1.0.0", - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sqlite3/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/sqlite3/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "optional": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sqlite3/node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "license": "MIT", - "optional": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sqlite3/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/sqlite3/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sqlite3/node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sqlite3/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", - "optional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/sqlite3/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sqlite3/node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/sqlite3/node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "license": "MIT", - "optional": true, - "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "optionalDependencies": { - "encoding": "^0.1.12" - } - }, - "node_modules/sqlite3/node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/sqlite3/node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sqlite3/node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/sqlite3/node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/sqlite3/node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/sqlite3/node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "license": "ISC", - "optional": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, "node_modules/ssri": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", @@ -10437,10 +11285,35 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-to-array": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/stream-to-array/-/stream-to-array-2.3.0.tgz", + "integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -10450,7 +11323,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -10477,11 +11350,51 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stringify-object/node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -10568,10 +11481,36 @@ "node": ">=4" } }, + "node_modules/stylus-lookup": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.0.0.tgz", + "integrity": "sha512-RaWKxAvPnIXrdby+UWCr1WRfa+lrPMSJPySte4Q6a+rWyjeJyFOLJxr5GrAVfcMCsfVlCuzTAJ/ysYT8p8do7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^12.0.0" + }, + "bin": { + "stylus-lookup": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylus-lookup/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/superjson": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", - "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10607,6 +11546,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -10614,10 +11570,21 @@ "dev": true, "license": "MIT" }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "license": "ISC", "dependencies": { "chownr": "^2.0.0", @@ -10631,28 +11598,11 @@ "node": ">=10" } }, - "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, "license": "MIT", "dependencies": { "bl": "^4.0.3", @@ -10669,6 +11619,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -10681,6 +11632,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -10693,6 +11645,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -10708,6 +11661,21 @@ "node": ">=4" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/text-extensions": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", @@ -10718,13 +11686,6 @@ "node": ">=0.10" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -10776,6 +11737,50 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", + "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -10786,15 +11791,6 @@ "node": ">=14.14" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10825,6 +11821,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -10836,9 +11843,9 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "license": "MIT", "engines": { @@ -10848,6 +11855,32 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-graphviz": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/ts-graphviz/-/ts-graphviz-2.1.5.tgz", + "integrity": "sha512-IigMCo40QZvyyURRdYFh0DV6DGDt7OqkPM/TBGXSJKfNKnYmOfRg0tzSlnJS1TQCWFSTEtpBQsqmAZcziXJrWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ts-graphviz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ts-graphviz" + } + ], + "license": "MIT", + "dependencies": { + "@ts-graphviz/adapter": "^2.0.6", + "@ts-graphviz/ast": "^2.0.6", + "@ts-graphviz/common": "^2.1.5", + "@ts-graphviz/core": "^2.0.6" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -10874,9 +11907,9 @@ } }, "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/tuf-js": { @@ -10894,18 +11927,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10919,6 +11940,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -10927,17 +11961,17 @@ "license": "MIT" }, "node_modules/typedoc": { - "version": "0.26.6", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.26.6.tgz", - "integrity": "sha512-SfEU3SH3wHNaxhFPjaZE2kNl/NFtLNW5c1oHsg7mti7GjmUj1Roq6osBQeMd+F4kL0BoRBBr8gQAuqBlfFu8LA==", + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.5.tgz", + "integrity": "sha512-x+fhKJtTg4ozXwKayh/ek4wxZQI/+2hmZUdO2i2NGDBRUflDble70z+ewHod3d4gRpXSO6fnlnjbDTnJk7HlkQ==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@gerrit0/mini-shiki": "^1.24.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "shiki": "^1.9.1", - "yaml": "^2.4.5" + "yaml": "^2.6.1" }, "bin": { "typedoc": "bin/typedoc" @@ -10946,53 +11980,37 @@ "node": ">= 18" }, "peerDependencies": { - "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" } }, "node_modules/typedoc-plugin-markdown": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.2.6.tgz", - "integrity": "sha512-k33o2lZSGpL3GjH28eW+RsujzCYFP0L5GNqpK+wa4CBcMOxpj8WV7SydNRLS6eSa2UvaPvNVJTaAZ6Tm+8GXoA==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-4.3.2.tgz", + "integrity": "sha512-hCF3V0axzbzGDYFW21XigWIJQBOJ2ZRVWWs7X+e62ew/pXnvz7iKF/zVdkBm3w8Mk4bmXWp/FT0IF4Zn9uBRww==", "dev": true, "license": "MIT", "engines": { "node": ">= 18" }, "peerDependencies": { - "typedoc": "0.26.x" + "typedoc": "0.27.x" } }, "node_modules/typedoc-vitepress-theme": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typedoc-vitepress-theme/-/typedoc-vitepress-theme-1.0.1.tgz", - "integrity": "sha512-pnpgzSQRaR9QLMl3it/tjq7vlV+eeUzKa22w/xF6ZUdAcYdmeag13kuA6EKfU7/kkIkJ/qsu1GPd3OcIC36Hlw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/typedoc-vitepress-theme/-/typedoc-vitepress-theme-1.1.1.tgz", + "integrity": "sha512-1UbhZdQIkGKLkIZCbw8putrel+Vo7KKFfd8RhQRSBgetUZGUJkum89kIyF3+Kzy+1nqE56/MLKVxpPgQYubYYg==", "dev": true, "license": "MIT", "peerDependencies": { - "typedoc-plugin-markdown": ">=4.1.0" - } - }, - "node_modules/typedoc/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "typedoc-plugin-markdown": ">=4.3.0" } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "devOptional": true, + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11002,6 +12020,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.0.tgz", + "integrity": "sha512-Xq2rRjn6tzVpAyHr3+nmSg1/9k9aIHnJ2iZeOH7cfGOWqTkXTm3kwpQglEuLGdNrYvPF+2gtAs+/KF5rjVo+WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.18.0", + "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/utils": "8.18.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -11024,9 +12065,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, "node_modules/unique-filename": { @@ -11055,6 +12096,79 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universal-user-agent": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", @@ -11097,6 +12211,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/uuid": { @@ -11134,15 +12249,45 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz", - "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", + "postcss": "^8.4.43", "rollup": "^4.20.0" }, "bin": { @@ -11194,65 +12339,54 @@ } } }, - "node_modules/vite/node_modules/rollup": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", - "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "node_modules/vite-node": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.5" + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" }, "bin": { - "rollup": "dist/bin/rollup" + "vite-node": "vite-node.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^18.0.0 || >=20.0.0" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.2", - "@rollup/rollup-android-arm64": "4.21.2", - "@rollup/rollup-darwin-arm64": "4.21.2", - "@rollup/rollup-darwin-x64": "4.21.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", - "@rollup/rollup-linux-arm-musleabihf": "4.21.2", - "@rollup/rollup-linux-arm64-gnu": "4.21.2", - "@rollup/rollup-linux-arm64-musl": "4.21.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", - "@rollup/rollup-linux-riscv64-gnu": "4.21.2", - "@rollup/rollup-linux-s390x-gnu": "4.21.2", - "@rollup/rollup-linux-x64-gnu": "4.21.2", - "@rollup/rollup-linux-x64-musl": "4.21.2", - "@rollup/rollup-win32-arm64-msvc": "4.21.2", - "@rollup/rollup-win32-ia32-msvc": "4.21.2", - "@rollup/rollup-win32-x64-msvc": "4.21.2", - "fsevents": "~2.3.2" + "funding": { + "url": "https://opencollective.com/vitest" } }, "node_modules/vitepress": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.3.4.tgz", - "integrity": "sha512-I1/F6OW1xl3kW4PaIMC6snxjWgf3qfziq2aqsDoFc/Gt41WbcRv++z8zjw8qGRIJ+I4bUW7ZcKFDHHN/jkH9DQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.5.0.tgz", + "integrity": "sha512-q4Q/G2zjvynvizdB3/bupdYkCJe2umSAMv9Ju4d92E6/NXJ59z70xB0q5p/4lpRyAwflDsbwy1mLV9Q5+nlB+g==", "dev": true, "license": "MIT", "dependencies": { - "@docsearch/css": "^3.6.1", - "@docsearch/js": "^3.6.1", - "@shikijs/core": "^1.13.0", - "@shikijs/transformers": "^1.13.0", + "@docsearch/css": "^3.6.2", + "@docsearch/js": "^3.6.2", + "@iconify-json/simple-icons": "^1.2.10", + "@shikijs/core": "^1.22.2", + "@shikijs/transformers": "^1.22.2", + "@shikijs/types": "^1.22.2", "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^5.1.2", - "@vue/devtools-api": "^7.3.8", - "@vue/shared": "^3.4.38", - "@vueuse/core": "^11.0.0", - "@vueuse/integrations": "^11.0.0", - "focus-trap": "^7.5.4", + "@vitejs/plugin-vue": "^5.1.4", + "@vue/devtools-api": "^7.5.4", + "@vue/shared": "^3.5.12", + "@vueuse/core": "^11.1.0", + "@vueuse/integrations": "^11.1.0", + "focus-trap": "^7.6.0", "mark.js": "8.11.1", "minisearch": "^7.1.0", - "shiki": "^1.13.0", - "vite": "^5.4.1", - "vue": "^3.4.38" + "shiki": "^1.22.2", + "vite": "^5.4.10", + "vue": "^3.5.12" }, "bin": { "vitepress": "bin/vitepress.js" @@ -11270,17 +12404,84 @@ } } }, - "node_modules/vue": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz", - "integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==", + "node_modules/vitest": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.38", - "@vue/compiler-sfc": "3.4.38", - "@vue/runtime-dom": "3.4.38", - "@vue/server-renderer": "3.4.38", - "@vue/shared": "3.4.38" + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" @@ -11298,6 +12499,16 @@ "dev": true, "license": "ISC" }, + "node_modules/walkdir": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.4.1.tgz", + "integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", @@ -11330,7 +12541,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -11342,11 +12553,28 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" @@ -11407,6 +12635,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { @@ -11570,12 +12799,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", - "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "dev": true, "license": "ISC", "bin": { @@ -11627,31 +12857,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/primitives": { - "name": "@xo/primitives", + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/docs": { + "name": "@xocash/docs", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "@xocash/primitives": "*", + "@xocash/stack": "*" + }, + "devDependencies": { + "typedoc": "^0.27.5", + "typedoc-plugin-markdown": "^4.3.2", + "typedoc-vitepress-theme": "^1.1.1", + "vitepress": "^1.3.4" + } + }, + "packages/json-payment-protocol-v2": { + "name": "@xocash/json-payment-protocol-v2", "version": "0.0.1", "license": "ISC" }, - "packages/vue": { - "name": "@xo/vue", + "packages/primitives": { + "name": "@xocash/primitives", "version": "0.0.1", - "license": "ISC", - "dependencies": { - "@xo/primitives": "^0.0.1", - "@xo/wallet": "^0.0.1", - "vue": "^3.4.38" - } + "license": "ISC" }, - "packages/wallet": { - "name": "@xo/wallet", + "packages/stack": { + "name": "@xocash/stack", "version": "0.0.1", "license": "ISC", "dependencies": { - "@electrum-cash/network": "^3.3.0", - "@xo/primitives": "^0.0.1", - "idb-keyval": "^6.2.1", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7" + "@electrum-cash/network": "^4.1.1", + "@electrum-cash/protocol": "2.1.0-development.8075178089", + "@xocash/primitives": "*", + "idb": "^8.0.0" } } } diff --git a/package.json b/package.json index 9b08e04..e1d4c1e 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,25 @@ "packages/*" ], "scripts": { + "bench": "lerna run bench", "build": "lerna run build", - "build:primitives": "lerna run build --scope=@xo/primitives --include-dependencies", - "build:wallet": "lerna run build --scope=@xo/wallet --include-dependencies", - "build:vue": "lerna run build --scope=@xo/vue --include-dependencies", - "docs:api": "typedoc --skipErrorChecking", - "docs:dev": "npm run docs:api && vitepress dev docs", - "docs:build": "npm run docs:api && vitepress build docs", - "docs:preview": "npm run docs:api && vitepress preview docs", + "build:primitives": "lerna run build --scope=@xocash/primitives --include-dependencies", + "build:stack": "lerna run build --scope=@xocash/stack --include-dependencies", + "build:vue": "lerna run build --scope=@xocash/vue --include-dependencies", + "build:json-payment-protocol-v2": "lerna run build --scope=@xocash/json-payment-protocol-v2 --include-dependencies", + "docs:api": "lerna run api --scope=@xocash/docs --include-dependencies", + "docs:dev": "lerna run dev --scope=@xocash/docs --include-dependencies", + "docs:build": "lerna run build --scope=@xocash/docs --include-dependencies", + "docs:preview": "lerna run preview --scope=@xocash/docs --include-dependencies", "format": "prettier --write \"**/*.{js,ts,md,json}\" --ignore-path .gitignore", "lint": "eslint .", - "test": "ava" + "madge": "npx madge --circular ./packages", + "test": "lerna run test", + "test:coverage": "lerna run test:coverage", + "version:patch": "lerna version patch", + "version:minor": "lerna version minor", + "version:major": "lerna version major", + "watch": "lerna run watch --parallel" }, "repository": { "type": "git", @@ -43,16 +51,19 @@ "@bitauth/libauth": "^3.0.0" }, "devDependencies": { + "@eslint/js": "^9.10.0", + "@types/eslint__js": "^8.42.3", "@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/parser": "^8.5.0", + "@vitest/coverage-v8": "^2.1.6", "eslint": "^9.10.0", - "eslint-config-prettier": "^8.1.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-prettier": "^5.2.1", "lerna": "^8.1.8", - "prettier": "^2.5.1", - "typedoc": "^0.26.6", - "typedoc-plugin-markdown": "^4.2.6", - "typedoc-vitepress-theme": "^1.0.1", - "typescript": "^5.1.3", - "vitepress": "^1.3.4" + "madge": "^8.0.0", + "prettier": "^3.3.3", + "typescript": "5.7.2", + "typescript-eslint": "^8.6.0", + "vitest": "^2.1.6" } } diff --git a/packages/docs/.gitignore b/packages/docs/.gitignore new file mode 100644 index 0000000..c390202 --- /dev/null +++ b/packages/docs/.gitignore @@ -0,0 +1,8 @@ +# Ignore the generated Vitepress build. +dist/ + +# Ignore Generated API Markdown Files +src/api + +# Ignore Vitepress Cache +src/.vitepress/cache diff --git a/packages/docs/package.json b/packages/docs/package.json new file mode 100644 index 0000000..b154866 --- /dev/null +++ b/packages/docs/package.json @@ -0,0 +1,25 @@ +{ + "name": "@xocash/docs", + "version": "0.0.1", + "description": "XO Cash Stack Documentation", + "private": true, + "type": "module", + "scripts": { + "api": "typedoc --skipErrorChecking", + "build": "npm run api && vitepress build src", + "dev": "npm run api && vitepress dev src", + "preview": "npm run api && vitepress preview src" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@xocash/primitives": "*", + "@xocash/stack": "*" + }, + "devDependencies": { + "typedoc": "^0.27.5", + "typedoc-plugin-markdown": "^4.3.2", + "typedoc-vitepress-theme": "^1.1.1", + "vitepress": "^1.3.4" + } +} diff --git a/packages/docs/src/.vitepress/config.ts b/packages/docs/src/.vitepress/config.ts new file mode 100644 index 0000000..ee86dd8 --- /dev/null +++ b/packages/docs/src/.vitepress/config.ts @@ -0,0 +1,215 @@ +import { defineConfig } from 'vitepress'; +import typedocSidebar from '../api/typedoc-sidebar.json'; + +// Special cases map for acronyms and exceptions +const SPECIAL_CASES = { + hd: 'HD', + indexeddb: 'IndexedDB', + inmemory: 'InMemory', + json: 'JSON', + p2p: 'P2P', + p2pkh: 'P2PKH', + p2sh: 'P2SH', + p2sh20: 'P2SH20', + p2sh32: 'P2SH32', + sse: 'SSE', + utxo: 'UTXO', +}; + +function toPascalCase(str: string) { + // Handle special cases first + const lowerStr = str.toLowerCase(); + + if (lowerStr.startsWith('@')) { + return str; + } + + // Split by common delimiters (hyphen, space, underscore) + return str + .split(/[-\s_]/) + .map((word) => { + if (word.length === 0) return ''; + + if (SPECIAL_CASES[word]) { + return SPECIAL_CASES[word]; + } + + // Capitalize first letter, lowercase rest + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(''); +} + +function convertNavToPascalCase(nav: Array) { + return nav.map((item) => { + const result = { ...item }; + + // Convert the text field + if (result.text) { + // If there was a @ prefix, preserve it + result.text = item.text.startsWith('@') + ? result.text + : toPascalCase(result.text); + } + + // Recursively process nested items + if (result.items && Array.isArray(result.items)) { + result.items = convertNavToPascalCase(result.items); + } + + return result; + }); +} + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + outDir: '../dist', + title: 'Stack', + description: + 'XO Stack: Building smart applications on the Bitcoin Cash Network', + themeConfig: { + // Set the logo + logo: { + light: '/logo-light.svg', + dark: '/logo-dark.svg', + }, + + // Enable search functionality. + // TODO: Re-enable once we release documentation. + /* + search: { + provider: 'local', + }, + */ + + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + // TODO: Re-enable once we release documentation + // { text: 'Documentation', link: '/quick-start' }, + { text: 'Tools', link: '/tools/primitives' }, + ], + + sidebar: { + // Default Side Menu. + '/': [ + { + text: 'Guide', + items: [ + { text: 'Quick Start', link: '/quick-start' }, + { text: 'Best Practices', link: '/api-examples' }, + ], + }, + { + text: 'Frameworks', + items: [ + { text: 'Vue', link: '/frameworks/vue' }, + { text: 'React', link: '/frameworks/react' }, + { text: 'Svelte', link: '/frameworks/svelte' }, + ], + }, + { + text: 'API', + items: convertNavToPascalCase(typedocSidebar), + }, + ], + // Side-menu for all routes under /tools/ + '/tools/': [ + { + text: 'XO Tools', + items: [ + { + text: 'Primitives', + link: '/tools/primitives', + collapsed: false, + items: [ + { text: 'Address', link: '/tools/primitives#address' }, + { + text: 'Block Header', + link: '/tools/primitives#block-header', + }, + { text: 'Bytes', link: '/tools/primitives#bytes' }, + { text: 'Hasher', link: '/tools/primitives#hasher' }, + { + text: 'HD Private Node', + link: '/tools/primitives#hd-private-node', + }, + { + text: 'HD Public Node', + link: '/tools/primitives#hd-public-node', + }, + { + text: 'Mnemonic', + link: '/tools/primitives#mnemonic', + }, + { + text: 'Private Key', + link: '/tools/primitives#private-key', + }, + { + text: 'Public Key', + link: '/tools/primitives#public-key', + }, + { + text: 'Satoshis', + link: '/tools/primitives#satoshis', + }, + { + text: 'Script', + link: '/tools/primitives#script', + }, + { + text: 'Transaction', + link: '/tools/primitives#transaction', + }, + ], + }, + /* + { + text: 'Stack', + collapsed: false, + items: [ + { + text: 'Blockchain', + items: [{ text: 'Blockchain Electrum' }], + }, + ], + }, + */ + ], + }, + ], + }, + /* + + */ + + socialLinks: [ + { icon: 'github', link: 'https://gitlab.com/GeneralProtocols/xo/stack' }, + ], + + // This is the outline on the right-hand side of the page. + outline: { + level: [2, 4], + label: 'On this page', + }, + }, + + head: [['link', { rel: 'icon', href: '/favicon.png' }]], + + // Set the dev and build targets to "esnext" + // NOTE: We do this so that we can support top-level-await. + vite: { + esbuild: { + target: 'esnext', + }, + build: { + target: 'esnext', + }, + optimizeDeps: { + esbuildOptions: { + target: 'esnext', + }, + }, + }, +}); diff --git a/docs/.vitepress/theme/Layout.vue b/packages/docs/src/.vitepress/theme/Layout.vue similarity index 100% rename from docs/.vitepress/theme/Layout.vue rename to packages/docs/src/.vitepress/theme/Layout.vue diff --git a/packages/docs/src/.vitepress/theme/components/CopyButton.vue b/packages/docs/src/.vitepress/theme/components/CopyButton.vue new file mode 100644 index 0000000..470ae5c --- /dev/null +++ b/packages/docs/src/.vitepress/theme/components/CopyButton.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/packages/docs/src/.vitepress/theme/components/LabelledSection.vue b/packages/docs/src/.vitepress/theme/components/LabelledSection.vue new file mode 100644 index 0000000..a06a0ca --- /dev/null +++ b/packages/docs/src/.vitepress/theme/components/LabelledSection.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue b/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue new file mode 100644 index 0000000..a090234 --- /dev/null +++ b/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/packages/docs/src/.vitepress/theme/components/Tooltip.vue b/packages/docs/src/.vitepress/theme/components/Tooltip.vue new file mode 100644 index 0000000..c969736 --- /dev/null +++ b/packages/docs/src/.vitepress/theme/components/Tooltip.vue @@ -0,0 +1,76 @@ + + + + + \ No newline at end of file diff --git a/docs/.vitepress/theme/custom.css b/packages/docs/src/.vitepress/theme/custom.css similarity index 95% rename from docs/.vitepress/theme/custom.css rename to packages/docs/src/.vitepress/theme/custom.css index 816a720..fe51e07 100644 --- a/docs/.vitepress/theme/custom.css +++ b/packages/docs/src/.vitepress/theme/custom.css @@ -36,6 +36,10 @@ body { font-size: 12px; } +ul.VPDocOutlineItem.root { + font-weight: bold; +} + .VPSidebarItem.level-2.is-active > .item > .indicator, .VPSidebarItem.level-3.is-active > .item > .indicator, .VPSidebarItem.level-4.is-active > .item > .indicator, @@ -151,3 +155,10 @@ body { :root:is(.dark) .light-only { display: none; } + +/* Text colors */ + +.text-error { + color: #f46666; + overflow-wrap: break-word; +} diff --git a/packages/docs/src/.vitepress/theme/icons/copy.svg b/packages/docs/src/.vitepress/theme/icons/copy.svg new file mode 100644 index 0000000..f7e6ac8 --- /dev/null +++ b/packages/docs/src/.vitepress/theme/icons/copy.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/.vitepress/theme/index.ts b/packages/docs/src/.vitepress/theme/index.ts similarity index 93% rename from docs/.vitepress/theme/index.ts rename to packages/docs/src/.vitepress/theme/index.ts index cd1a452..196b9c0 100644 --- a/docs/.vitepress/theme/index.ts +++ b/packages/docs/src/.vitepress/theme/index.ts @@ -4,6 +4,7 @@ import DefaultTheme from 'vitepress/theme'; import Layout from './Layout.vue'; import './style.css'; import './custom.css'; +import './tools.css'; export default { extends: DefaultTheme, diff --git a/docs/.vitepress/theme/logo-dark.svg b/packages/docs/src/.vitepress/theme/logo-dark.svg similarity index 100% rename from docs/.vitepress/theme/logo-dark.svg rename to packages/docs/src/.vitepress/theme/logo-dark.svg diff --git a/docs/.vitepress/theme/logo-light.svg b/packages/docs/src/.vitepress/theme/logo-light.svg similarity index 100% rename from docs/.vitepress/theme/logo-light.svg rename to packages/docs/src/.vitepress/theme/logo-light.svg diff --git a/packages/docs/src/.vitepress/theme/style.css b/packages/docs/src/.vitepress/theme/style.css new file mode 100644 index 0000000..3a3125c --- /dev/null +++ b/packages/docs/src/.vitepress/theme/style.css @@ -0,0 +1,560 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #38b6ff 30%, + #5271ff + ); + + /* + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #5271ff 50%, + #38b6ff 50% + + ); + + --vp-home-hero-image-filter: blur(44px); + + */ +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + +/** + * Overrides + */ + +:root { + --primary: #5271ff; + --vp-nav-logo-height: 48px; +} + +html.dark { + --primary: #38b6ff; +} + +body { + font-size: 14px; +} + +.vp-nav .title { + font-size: 24px !important; +} + +.vp-doc h1 { + font-size: 24px; +} + +.vp-doc h2 { + font-size: 22px; + color: var(--primary); + font-weight: bold; + margin-top: 15px; + margin-bottom: 5px; + border: none !important; + line-height: 1.2; +} + +.vp-doc h3 { + font-size: 16px; +} + +.vp-doc h4 { + font-size: 14px; + opacity: 0.75; +} + +.VPSidebarItem .text { + font-size: 12px; +} + +.VPSidebarItem.level-2.is-active > .item > .indicator, +.VPSidebarItem.level-3.is-active > .item > .indicator, +.VPSidebarItem.level-4.is-active > .item > .indicator, +.VPSidebarItem.level-5.is-active > .item > .indicator { + background-color: var(--primary) !important; +} + +.VPSidebarItem.level-0.is-active > .item .link > .text, +.VPSidebarItem.level-1.is-active > .item .link > .text, +.VPSidebarItem.level-2.is-active > .item .link > .text, +.VPSidebarItem.level-3.is-active > .item .link > .text, +.VPSidebarItem.level-4.is-active > .item .link > .text, +.VPSidebarItem.level-5.is-active > .item .link > .text { + color: var(--primary) !important; +} + +.VPSidebarItem.level-0.is-link > .item > .link:hover .text, +.VPSidebarItem.level-1.is-link > .item > .link:hover .text, +.VPSidebarItem.level-2.is-link > .item > .link:hover .text, +.VPSidebarItem.level-3.is-link > .item > .link:hover .text, +.VPSidebarItem.level-4.is-link > .item > .link:hover .text, +.VPSidebarItem.level-5.is-link > .item > .link:hover .text { + color: var(--primary) !important; +} + +.outline-title { + font-size: 12px !important; +} + +.outline-link { + font-size: 12px !important; +} + +/* Customization of VitePress CSS */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #5271ff, + #38b6ff + ); +} + +.VPButton.brand { + background: linear-gradient(-45deg, #5271ff, #38b6ff) !important; +} + +.VPButton.brand:hover { + background: linear-gradient(45deg, #5271ff, #5271ff) !important; +} + +/* .vitepress/theme/custom.css */ + +.image-container { + display: flex; + justify-content: center; + align-items: center; + height: 100%; /* Ensure it takes up the full height of the parent */ + width: 100%; /* Ensure it takes up the full width of the parent */ +} + +/* .vitepress/theme/custom.css */ + +/* Container for all icons and arrows */ +.icon-container { + display: flex; + align-items: center; /* Align the icons and arrows vertically */ + justify-content: center; /* Center the icons and arrows horizontally */ + gap: 20px; /* Space between the icons and arrows */ +} + +/* BCH icon styling */ +.bch-icon { + width: 64px; + height: 64px; + transform: rotate(-15deg); /* Rotate the BCH icon by 15 degrees to the left */ + fill: currentColor; +} + +/* Stack icon styling */ +.stack-icon { + width: 64px; + height: 64px; + fill: currentColor; +} + +/* Apps icon styling */ +.apps-icon { + width: 64px; + height: 64px; + fill: currentColor; +} + +/* Arrow between Stack and BCH */ +.right-arrow { + width: 24px; + height: 24px; +} + +/* Arrow between BCH and Apps */ +.left-arrow { + width: 24px; + height: 24px; +} + +/* Light / dark mode logos */ + +:root:not(.dark) .dark-only { + display: none; +} + +:root:is(.dark) .light-only { + display: none; +} + +/* Feature box override */ + +.feature-box-or { + flex-direction: row !important; + text-align: center !important; +} + +.feature-box-or h2 { + border: none !important; + padding-top: 10px !important; + margin: 0 !important; +} + +.feature-box-or img { + margin: auto !important; +} + +/* Promo on front page */ + +.promo { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + padding: 20px; +} + +.promo-text { + flex: 2; /* Adjusted flex value for more text space */ + min-width: 250px; /* Ensures text is responsive */ +} + +.promo-text h1 { + font-size: 2rem; /* Adjust font size as needed */ + margin: 0; +} + +.promo-text p { + font-size: 1.1rem; /* Slightly larger text for better readability */ + margin-top: 10px; +} + +.promo-text .clip { + background: var(--vp-home-hero-name-background); + -webkit-background-clip: text; + background-clip: text; + color: var( + --vp-home-hero-name-color + ); /* Make the text color transparent to reveal background */ + font-weight: bold; + padding: 0 5px; +} + +.promo-image { + position: relative; /* To position the animated elements inside */ + flex: 1; /* Reduce the image's width */ + min-width: 150px; /* Ensures the image is smaller */ + max-width: 250px; /* Set a max width for the image */ + text-align: right; +} + +.promo-image img { + max-width: 100%; + height: auto; + position: relative; /* Image is above the animated icons */ + z-index: 1; /* Ensure image is in front of the icons */ +} + +/* X and O animation styles behind promo-image */ +.animated-bg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; /* Ignore background interactions */ + z-index: 0; /* Ensure the animated elements are behind the image */ +} + +.xo { + position: absolute; + font-size: 1.5rem; +} + +.xo-1, +.xo-3 { + color: var(--primary); /* X elements using --primary color */ +} + +.xo-2, +.xo-4 { + color: var(--vp-c-text-1); /* O elements using --vp-c-text-1 color */ +} + +/* Unique positions and animations for each XO element */ +.xo-1 { + top: 10%; + left: 15%; + animation: + rotate-small 6s infinite linear, + move-elliptical 10s infinite ease-in-out; +} + +.xo-2 { + top: 40%; + left: 60%; + animation: + rotate-large 8s infinite linear, + move-circular 12s infinite ease-in-out; +} + +.xo-3 { + top: 70%; + left: 30%; + animation: + rotate-reverse 5s infinite linear, + move-diagonal 14s infinite ease-in-out; +} + +.xo-4 { + top: 20%; + left: 80%; + animation: + rotate-small-reverse 7s infinite linear, + move-random 16s infinite ease-in-out; +} + +/* Keyframe for self-rotation: Small */ +@keyframes rotate-small { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +/* Keyframe for self-rotation: Large */ +@keyframes rotate-large { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(720deg); + } +} + +/* Keyframe for reverse self-rotation */ +@keyframes rotate-reverse { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-360deg); + } +} + +@keyframes rotate-small-reverse { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(-720deg); + } +} + +/* Keyframe for elliptical movement */ +@keyframes move-elliptical { + 0% { + transform: translate(0, 0); + } + 25% { + transform: translate(60px, 40px); + } + 50% { + transform: translate(120px, 0); + } + 75% { + transform: translate(60px, -40px); + } + 100% { + transform: translate(0, 0); + } +} + +/* Keyframe for circular movement */ +@keyframes move-circular { + 0% { + transform: translate(0, 0); + } + 25% { + transform: translate(50px, 50px); + } + 50% { + transform: translate(100px, 0); + } + 75% { + transform: translate(50px, -50px); + } + 100% { + transform: translate(0, 0); + } +} + +/* Keyframe for diagonal movement */ +@keyframes move-diagonal { + 0% { + transform: translate(0, 0); + } + 50% { + transform: translate(100px, 100px); + } + 100% { + transform: translate(0, 0); + } +} + +/* Keyframe for random, unpredictable movement */ +@keyframes move-random { + 0% { + transform: translate(0, 0); + } + 20% { + transform: translate(40px, -30px); + } + 40% { + transform: translate(80px, 50px); + } + 60% { + transform: translate(-50px, -40px); + } + 80% { + transform: translate(30px, 70px); + } + 100% { + transform: translate(0, 0); + } +} + +@media (max-width: 768px) { + .promo { + flex-direction: column; + text-align: center; + } + + .promo-image { + margin-top: 20px; + } +} + + diff --git a/packages/docs/src/.vitepress/theme/tools.css b/packages/docs/src/.vitepress/theme/tools.css new file mode 100644 index 0000000..f9ecff1 --- /dev/null +++ b/packages/docs/src/.vitepress/theme/tools.css @@ -0,0 +1,145 @@ +.tool h2 { + font-size: 16px; +} + +/* Wrapper for input section */ +.input-wrapper { + margin-bottom: 20px; +} + +/* Flex container for dropdown and input */ +.form-group-row { + display: flex; + flex-wrap: wrap; /* Ensure responsiveness */ + gap: 15px; /* Space between dropdown and input */ + margin-bottom: 15px; +} + +.form-group-row.extended { + flex-direction: column; +} + +/* Form group styling */ +.form-group { + flex: 1; + min-width: 200px; +} + +.select-group { + flex: 1; + min-width: 200px; + max-width: 50%; +} + +.input-group { + flex: 1; + min-width: 200px; +} + +.input-group.extended { + max-width: 100%; + width: 100%; +} + +.form-group label { + display: block; + font-weight: bold; + margin-bottom: 5px; +} + +.form-group select, +.form-group input, +.form-group button { + width: 100%; /* Makes fields responsive */ + padding: 10px; + font-size: 1rem; + border: 1px solid #ccc; + border-radius: 4px; +} + +/* Extended input styling */ +.extended-input { + width: 100%; + min-height: 2.5rem; + max-height: 300px; + resize: none; + padding: 0.5rem; + line-height: 1.5; + font-family: inherit; + font-size: inherit; + border: 1px solid var(--vp-c-divider); + border-radius: 4px; + background: var(--vp-c-bg-alt); +} + +.form-group button { + background-color: #333; + cursor: pointer; +} + +.form-group button:hover { + background-color: #555; +} + +/* Output section */ +.output-section { + margin-top: 20px; + margin-bottom: 50px; +} + +.output-box-container { + position: relative; + margin-top: 15px; +} + +.output-box-label { + font-size: 0.8em; +} + +.output-box { + box-sizing: content-box; + border: 1px dashed #ccc; + padding: 10px; + min-height: 24px; + overflow-wrap: break-word; + white-space: pre-wrap; + font-family: monospace; +} + +.placeholder { + text-align: center; + color: #888; + font-style: italic; + margin-top: 20px; +} + +/* Gradient Button Styling */ +.gradient-button { + padding: 10px 20px; + font-size: 1rem; + font-weight: bold; + color: white; + background: var(--vp-home-hero-name-background); + border: none; + width: 100%; + margin: auto; + border-radius: 10px; /* Full-rounded button */ + cursor: pointer; + text-transform: uppercase; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); /* Soft shadow */ +} + +.gradient-button:hover { + background: var(--primary); +} + +/* Mobile responsiveness */ +@media (max-width: 768px) { + .select-group { + max-width: 100%; + } + + .input-group { + width: 100%; + } +} diff --git a/docs/api-examples.md b/packages/docs/src/api-examples.md similarity index 99% rename from docs/api-examples.md rename to packages/docs/src/api-examples.md index 6bd8bb5..691df9c 100644 --- a/docs/api-examples.md +++ b/packages/docs/src/api-examples.md @@ -18,12 +18,15 @@ const { theme, page, frontmatter } = useData() ## Results ### Theme Data +
{{ theme }}
### Page Data +
{{ page }}
### Page Frontmatter +
{{ frontmatter }}
``` @@ -36,12 +39,15 @@ const { site, theme, page, frontmatter } = useData() ## Results ### Theme Data +
{{ theme }}
### Page Data +
{{ page }}
### Page Frontmatter +
{{ frontmatter }}
## More diff --git a/docs/frameworks/react.md b/packages/docs/src/frameworks/react.md similarity index 98% rename from docs/frameworks/react.md rename to packages/docs/src/frameworks/react.md index 4e5ba24..4ff2400 100644 --- a/docs/frameworks/react.md +++ b/packages/docs/src/frameworks/react.md @@ -7,4 +7,3 @@ npm install @xo/react ``` ## Usage - diff --git a/docs/frameworks/svelte.md b/packages/docs/src/frameworks/svelte.md similarity index 98% rename from docs/frameworks/svelte.md rename to packages/docs/src/frameworks/svelte.md index 2db9b44..1c1607f 100644 --- a/docs/frameworks/svelte.md +++ b/packages/docs/src/frameworks/svelte.md @@ -7,4 +7,3 @@ npm install @xo/svelte ``` ## Usage - diff --git a/docs/frameworks/vue.md b/packages/docs/src/frameworks/vue.md similarity index 100% rename from docs/frameworks/vue.md rename to packages/docs/src/frameworks/vue.md diff --git a/docs/gp_logo_dark.png b/packages/docs/src/gp_logo_dark.png similarity index 100% rename from docs/gp_logo_dark.png rename to packages/docs/src/gp_logo_dark.png diff --git a/docs/gp_logo_light.png b/packages/docs/src/gp_logo_light.png similarity index 100% rename from docs/gp_logo_light.png rename to packages/docs/src/gp_logo_light.png diff --git a/packages/docs/src/index.md b/packages/docs/src/index.md new file mode 100644 index 0000000..cffdf1d --- /dev/null +++ b/packages/docs/src/index.md @@ -0,0 +1,169 @@ +--- +# VitePress configuration +layout: home + +hero: + name: 'XO Stack' + text: 'On Bitcoin Cash' + tagline: Building smart applications on the BCH UTXO Network + actions: + - theme: brand + text: Tools + link: /tools/primitives + - theme: brand + text: GitLab + link: https://gitlab.com/GeneralProtocols/xo/stack +--- + + + + +
+
+

Create robust scalable Bitcoin Cash apps, faster than ever before.

+

XO provides cutting-edge tools to help developers create Bitcoin Cash apps with ease and efficiency. With a focus on scalability, security, and speed, you can bring your app to life in no time.

+

Start building now and unlock the full potential of Bitcoin Cash in the digital economy!

+
+
+ +
+ x + o + o + x +
+ Stack Light Image +
+
+ + +
+
+

Stack Features

+
+
+ +
+
+ +
+
+ +
+
+
+
+ + +
+

Developed by:

+
+ + + diff --git a/packages/docs/src/meeting-notes/2024-10-11.md b/packages/docs/src/meeting-notes/2024-10-11.md new file mode 100644 index 0000000..377933e --- /dev/null +++ b/packages/docs/src/meeting-notes/2024-10-11.md @@ -0,0 +1,75 @@ +### Double-Spends and Detecting Conflicting State + +1. Currently DSP's are constrained to P2PKH. +2. We may be able to extend these to **some** P2SH - but the requirement is that the P2SH must have an `OP_CHECKSIG`. +3. There will be at least some (potentially many) kinds of P2SH that lack an `OP_CHECKSIG`. +4. One problem that we have is that we cannot reliably determine (with Electrum) whether a P2SH may have been double-spent. +5. ??? +6. Lack of profit + +The point you're trying to get at is that Nodes on the network can have a differing mempool state. + +How do we detect that? Fulcrum can't really provide us with that information. + +How do we handle it if we can detect it? E.g. avoiding any "contentious" dependencies. + +Do we have to store a whole graph? + +For now, do owe just broadcast to one of our nodes and then broadcast to ALL of our nodes 5s later to see if there's contention? + +### Electrum 4.0.1 Notes + +1. Maybe default constructor generic to `ElectrumClientEvents`: + +```ts +class ElectrumClient +``` + +2. `RequestResponse` might be typed incorrectly: It now takes `RPCParameter | RPCParameter[]` which are defined as follows: `string | number | boolean | null;` (note that it only accept parameters, not responses which are often/maybe-always JSON). + +Previously, it would accept: + +``` +export type RequestResponse = + | object + | string + | number + | boolean + | null + | RequestResponse[]; +``` + +Perhaps we could type it: `export type RequestResponse = RPCParameter & object | Array;` (replace `object` with something like `RPCResponse`, but I think that might be taken already?) + +3. `RPCNotification` might be typed incorrectly (or maybe this is for future proofing?). The `params` property is marked as optional (but I think all notifications will have at least one). Might be more elegant to have it required and just return an empty array. + +4. The current approach still requires polyfills because `@electrum/sockets` itself is imported by `@electrum/network` and `@electrum/sockets` exports `ElectrumTcpSocket`. + + A workable approach might be: + + 1. Move socket interface back to `@electum/network`. + 2. Move the `ElectrumWebSocket` back to `@electrum/network`. + 3. Rename `@electrum/sockets` to `@electrum/tcp-socket` and only include the TCP Socket in there. + 4. This will still allow third-parties to define their own sockets (e.g. `@custom/electrum-libp2p-socket`). + +5. This is what's required for polyfilling in Vite currently: + +```sh +npm install events +npm install vite-plugin-node-polyfills +``` + +```js + [ + nodePolyfills, + { + include: ['events', 'net', 'tls'], + // NOTE: Not disabling these causes an error on Vite Polyfills V0.22. + globals: { + Buffer: false, + global: false, + process: false, + }, + }, + ], +``` diff --git a/docs/public/favicon.png b/packages/docs/src/public/favicon.png similarity index 100% rename from docs/public/favicon.png rename to packages/docs/src/public/favicon.png diff --git a/docs/public/gp_logo_dark.png b/packages/docs/src/public/gp_logo_dark.png similarity index 100% rename from docs/public/gp_logo_dark.png rename to packages/docs/src/public/gp_logo_dark.png diff --git a/docs/public/gp_logo_light.png b/packages/docs/src/public/gp_logo_light.png similarity index 100% rename from docs/public/gp_logo_light.png rename to packages/docs/src/public/gp_logo_light.png diff --git a/docs/public/icons/api-dark.svg b/packages/docs/src/public/icons/api-dark.svg similarity index 100% rename from docs/public/icons/api-dark.svg rename to packages/docs/src/public/icons/api-dark.svg diff --git a/docs/public/icons/api-light.svg b/packages/docs/src/public/icons/api-light.svg similarity index 100% rename from docs/public/icons/api-light.svg rename to packages/docs/src/public/icons/api-light.svg diff --git a/docs/public/icons/framework-dark.svg b/packages/docs/src/public/icons/framework-dark.svg similarity index 100% rename from docs/public/icons/framework-dark.svg rename to packages/docs/src/public/icons/framework-dark.svg diff --git a/docs/public/icons/framework-light.svg b/packages/docs/src/public/icons/framework-light.svg similarity index 100% rename from docs/public/icons/framework-light.svg rename to packages/docs/src/public/icons/framework-light.svg diff --git a/packages/docs/src/public/icons/stack_light.png b/packages/docs/src/public/icons/stack_light.png new file mode 100644 index 0000000..70d5123 Binary files /dev/null and b/packages/docs/src/public/icons/stack_light.png differ diff --git a/docs/public/icons/tools-dark.svg b/packages/docs/src/public/icons/tools-dark.svg similarity index 100% rename from docs/public/icons/tools-dark.svg rename to packages/docs/src/public/icons/tools-dark.svg diff --git a/docs/public/icons/tools-light.svg b/packages/docs/src/public/icons/tools-light.svg similarity index 100% rename from docs/public/icons/tools-light.svg rename to packages/docs/src/public/icons/tools-light.svg diff --git a/docs/public/logo-dark.svg b/packages/docs/src/public/logo-dark.svg similarity index 100% rename from docs/public/logo-dark.svg rename to packages/docs/src/public/logo-dark.svg diff --git a/docs/public/logo-light.svg b/packages/docs/src/public/logo-light.svg similarity index 100% rename from docs/public/logo-light.svg rename to packages/docs/src/public/logo-light.svg diff --git a/packages/docs/src/quick-start.md b/packages/docs/src/quick-start.md new file mode 100644 index 0000000..1c52b5d --- /dev/null +++ b/packages/docs/src/quick-start.md @@ -0,0 +1,218 @@ +# Quickstart + +Install the XO Stack + +```sh +npm install @xo/stack +``` + +Example Usage: + +```ts +// Import XO Modules. +import { Address, BlockchainElectrum, StorageMemory } from '@xo/stack'; + +// The Electrum Servers we will be using. +const ELECTRUM_SERVERS = [ + 'bch.imaginary.cash', + 'cashnode.bch.ninja', + 'electrum.imaginary.cash', +]; + +// Create an instance of the services that WalletHD will require. +// NOTE: Other storage adapters currently broken, in the midst of refactor. +const blockchainElectrum = new BlockchainElectrum(ELECTRUM_SERVERS); +const cachesStore = new StorageMemory(); +const walletsStore = new StorageMemory(); + +// Start each of our services. +await Promise.all([ + blockchainElectrum.start(), + cachesStore.start(), + walletsStore.start(), +]); + +// Create an instance of a HDWallet. +const walletHD = await Wallet.from( + { + derivationPath: "m/44'/145'/0'", + mnemonic: + 'page pencil stock planet limb cluster assault speak off joke private pioneer', + }, + { + blockchain: blockchainElectrum, + cache: cachesStore, + storage: walletsStore, + }, +); + +// Scan the wallet and start monitoring addresess for transactions. +await walletHD.start(); + +// Send a transaction to the network, automatically populating UTXOs to match the output amount. +// NOTE: Pre-signed inputs can be provided and change outputs will be automatically appended. +const txResult = await walletHD.send({ + outputs: [ + { + lockingBytecode: Address.fromCashAddr( + 'bitcoincash:qpc7mdjncqzf96vfzfaaqfrmru4rpg5qeq4m237t78', + ).toLockscriptBytes(), + valueSatoshis: 10_000, + }, + ], +}); +``` + +## Structure/Patterns + +General structure: + +- All blockchain adapters will implement `IBlockhain` +- All storage adapters will implement `IStorage` +- All wallets will implement `IWallet` +- Likewise for any other "service" we need in XO Stack (e.g. BCMR?, Prices?). + +Most (almost all?) service methods in our interfaces will be `async`. This is so that we can handle situations where things might be remote (or require OS approval of some sort). For example: + +1. Storage Adapters will need to allow backup to third-party services (so the methods must be `async`). +2. Some Wallets **may** have remote dependencies (e.g. imagine a Multisig, Co-pay-like service). +3. Wallets on IOS may have to tie in, in some way, with the host OS (to get approvals). +4. There is also the `ServiceWorker` ideal where we would like to be able to run everything in a `ServiceWorker` and communicate to/from it from frontend. I'm not sure whether this will be realizable in practice yet. + +The approach here aims to be extendable: If there is something that the XO Stack does not support, someone can: + +1. Extend existing XO classes to customize it to their own needs +2. BYO their own adapter +3. Create their own wallet type that can still leverage other parts of XO stack + +### Wallets + +Wallets can be **composed** of other Wallets. For example: + +- WalletP2PKH is standalone +- WalletHD **has many** WalletP2PKH +- SomeCustomWallet **may have many** WalletHD (or other kinds of wallet). + +The only requirement is that a Wallet **MUST** implement the IWallet interface. + +### Blockchain + +Blockchains represent a connection to the BCH Network ("Blockchain" might not be the best term, but let's bikeshed over that later). This blockchain might be: + +- Via Electrum-Cash (current implementation) +- Via Chaingraph (not sure if we'll implement this, but it probably wouldn't be too difficult) +- Local (as in, something like an in-memory "regtest") + +We will likely want redundancies or possibly a fallback to a "local" blockchain in some cases where a device is disconnected from the internet (e.g. online merchant, offline customer). + +The current plan (not implemented yet) is to create a main `Blockchain` class that can be instantiated something like follows: + +```ts +const blockchain = new Blockchain([ + new BlockchainElectrum(...), + new BlockchainLocal(...), +]); +``` + +This `Blockchain` class will still implement the `IBlockchain` interface, but will try to direct requests to the correct Blockchain instance. This still needs more fleshing out. + +### Stores + +A **store** is just a means of storing data. It can be in-memory, to a file (e.g. SQLite), to somewhere remote (Firebase, MySQL, MongoDB, etc). + +It is hierachical, meaning it can be structured like a tree (not just a simple KV table). This is in anticipation of WalletSets which likely going to have complex data structures. Ideally, where possible, we'll want our adapters to only mutate the relevant portion of an entry as opposed to the ENTIRE entry for a wallet (which would increase I/O and the risk of corruption). + +As with `Blockchain`, we'll want support for redundancies (e.g. Local and Remote Backups). + +The current plan (not implemented yet) is to create a `Storage` class that can be instantiated something like follows: + +```ts +const storage = new Storage([ + // Local store on Mobile Devices. + new StorageSQLite(...), + // GP's storage service. + new StorageGPStore(...), +]); +``` + +This still needs a bit more fleshing out but, to allow syncing, UIDs (or WalletSet Activity IDs) will probably be used where possible (as opposed to autoincrement-like indexes). + +### Double-Spends and Detecting Conflicting State + +1. Currently DSP's are constrained to P2PKH. +2. We may be able to extend these to **some** P2SH - but the requirement is that the P2SH must have an `OP_CHECKSIG`. +3. There will be at least some (potentially many) kinds of P2SH that lack an `OP_CHECKSIG`. +4. One problem that we have is that we cannot reliably determine (with Electrum) whether a P2SH may have been double-spent. +5. ??? +6. Lack of profit + +The point you're trying to get at is that Nodes on the network can have a differing mempool state. + +How do we detect that? Fulcrum can't really provide us with that information. + +How do we handle it if we can detect it? E.g. avoiding any "contentious" dependencies. + +Do we have to store a whole graph? + +Do we just broadcast to one of our nodes and then broadcast to ALL of our nodes ~5s later to see if there's contention? + +### Electrum 4.0.1 Notes + +1. Maybe default constructor generic to `ElectrumClientEvents`: + +```ts +class ElectrumClient +``` + +2. `RequestResponse` might be typed incorrectly: It now takes `RPCParameter | RPCParameter[]` which are defined as follows: `string | number | boolean | null;` (note that it only accept parameters, not responses which are often/maybe-always JSON). + +Previously, it would accept: + +``` +export type RequestResponse = + | object + | string + | number + | boolean + | null + | RequestResponse[]; +``` + +Perhaps we could type it: `export type RequestResponse = RPCParameter & object | Array;` (replace `object` with something like `RPCResponse`, but I think that might be taken already?) + +3. `RPCNotification` might be typed incorrectly (or maybe this is for future proofing?). The `params` property is marked as optional (but I think all notifications will have at least one). Might be more elegant to have it required and just return an empty array. + +4. The current approach still requires polyfills because `@electrum/sockets` itself is imported by `@electrum/network` and `@electrum/sockets` exports `ElectrumTcpSocket`. + +A workable approach might be: + +1. Move socket interface back to `@electum/network`. +2. Move the `ElectrumWebSocket` back to `@electrum/network`. +3. Rename `@electrum/sockets` to `@electrum/tcp-socket` and only include the TCP Socket in there. +4. This will still allow third-parties to define their own sockets (e.g. `@custom/electrum-libp2p-socket`). + +5. This is what's required for polyfilling in Vite currently: + +```sh +npm install events +npm install vite-plugin-node-polyfills +``` + +```js + [ + nodePolyfills, + { + include: ['events', 'net', 'tls'], + // NOTE: Not disabling these causes an error on Vite Polyfills V0.22. + globals: { + Buffer: false, + global: false, + process: false, + }, + }, + ], +``` + +## Markdown Extensions (TODO: Remove me later) + +Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/packages/docs/src/tools/index.md b/packages/docs/src/tools/index.md new file mode 100644 index 0000000..2d08ac6 --- /dev/null +++ b/packages/docs/src/tools/index.md @@ -0,0 +1,6 @@ + + + +TODO: Once we have other tools, we might want to turn this into a page like that on Quick Start that presents a "box" of each Tool Group. diff --git a/packages/docs/src/tools/primitive-functions.ts b/packages/docs/src/tools/primitive-functions.ts new file mode 100644 index 0000000..4941d39 --- /dev/null +++ b/packages/docs/src/tools/primitive-functions.ts @@ -0,0 +1,605 @@ +import { + Address, + BlockHeader, + Bytes, + encodeExtendedJson, + decodeExtendedJson, + Hasher, + HDPrivateNode, + HDPublicNode, + Mnemonic, + PrivateKey, + PublicKey, + Satoshis, + Script, + Transaction, +} from '@xocash/primitives'; + +export const address = { + from: [ + { + label: 'From', + inputs: ['CashAddr or Legacy Address'], + models: [''], + callback: (args: [string]) => Address.from(...args), + extendedInput: true, + }, + { + label: 'From CashAddr', + inputs: ['CashAddr'], + models: [''], + callback: (args: [string]) => Address.fromCashAddr(...args), + extendedInput: true, + }, + { + label: 'From Legacy', + inputs: ['Legacy Address'], + models: [''], + callback: (args: [string]) => Address.fromLegacy(...args), + extendedInput: true, + }, + { + label: 'From Redeem Script Hex (P2SH20)', + inputs: ['RedeemScript Hex'], + models: [''], + callback: (args: [string]) => + Address.fromRedeemScriptHex(...args, 'P2SH20'), + extendedInput: true, + }, + { + label: 'From Redeem Script Hex (P2SH32)', + inputs: ['RedeemScript Hex'], + models: [''], + callback: (args: [string]) => + Address.fromRedeemScriptHex(...args, 'P2SH32'), + extendedInput: true, + }, + { + label: 'From Lockscript Hex', + inputs: ['Lockscript Hex'], + models: [''], + callback: (args: [string]) => Address.fromLockscriptHex(...args), + extendedInput: true, + }, + { + label: 'From Hash160 Hex', + inputs: ['Hash160 Hex'], + models: [''], + callback: (args: [string]) => Address.fromHash160Hex(...args), + extendedInput: true, + }, + ], + to: [ + { + label: 'To CashAddr (mainnet)', + callback: (address: Address) => address.toCashAddr('bitcoincash'), + }, + { + label: 'To CashAddr (testnet)', + callback: (address: Address) => address.toCashAddr('bchtest'), + }, + { + label: 'To CashAddr (regtest)', + callback: (address: Address) => address.toCashAddr('bchreg'), + }, + { + label: 'To TokenAware CashAddr (mainnet)', + callback: (address: Address) => address.toTokenAddress('bitcoincash'), + }, + { + label: 'To TokenAware CashAddr (testnet)', + callback: (address: Address) => address.toTokenAddress('bchtest'), + }, + { + label: 'To TokenAware CashAddr (regtest)', + callback: (address: Address) => address.toTokenAddress('bchreg'), + }, + { + label: 'To Legacy', + callback: (address: Address) => address.toLegacy(), + }, + { + label: 'To Hash160/RIPEMD Hex', + callback: (address: Address) => address.toHash160Hex(), + }, + { + label: 'To Lockscript Hex', + callback: (address: Address) => address.toLockscriptHex(), + }, + { + label: 'To Lockscript ASM', + callback: (address: Address) => address.toLockscriptAsm(), + }, + ], +}; + +export const blockHeader = { + from: [ + { + label: 'From Hex', + inputs: ['UTF-8'], + models: [''], + callback: (args: [string]) => BlockHeader.fromHex(...args), + }, + ], + to: [ + { + label: 'To Raw', + callback: (blockHeader: BlockHeader) => blockHeader.toRaw(), + }, + { + label: 'Version', + callback: (blockHeader: BlockHeader) => blockHeader.getVersion(), + }, + { + label: 'Previous Block Hash', + callback: (blockHeader: BlockHeader) => + blockHeader.getPreviousBlockHashHex(), + }, + { + label: 'Merkle Root', + callback: (blockHeader: BlockHeader) => blockHeader.getMerkleRootHex(), + }, + { + label: 'Timestamp', + callback: (blockHeader: BlockHeader) => + blockHeader.getTimestamp().toString(), + }, + { + label: 'Bits', + callback: (blockHeader: BlockHeader) => blockHeader.getBits().toString(), + }, + { + label: 'Nonce', + callback: (blockHeader: BlockHeader) => blockHeader.getNonce().toString(), + }, + ], +}; + +export const bytes = { + from: [ + { + label: 'From Base64', + inputs: ['Base64'], + models: [''], + callback: (args: [string]) => Bytes.fromBase64(...args), + }, + { + label: 'From Hex', + inputs: ['Hex'], + models: [''], + callback: (args: [string]) => Bytes.fromHex(...args), + }, + { + label: 'From UTF-8', + inputs: ['UTF-8'], + models: [''], + callback: (args: [string]) => Bytes.fromUtf8(...args), + }, + ], + to: [ + { + label: 'To Base64', + callback: (bytes: Bytes) => bytes.toBase64(), + }, + { + label: 'To Hex', + callback: (bytes: Bytes) => bytes.toHex(), + }, + { + label: 'To UTF-8', + callback: (bytes: Bytes) => bytes.toUtf8(), + }, + ], +}; + +export const hasher = { + from: [ + { + label: 'From Hex', + inputs: ['Hex'], + models: [''], + callback: (args: [string]) => Hasher.fromHex(...args), + }, + { + label: 'From Base64', + inputs: ['Base64'], + models: [''], + callback: (args: [string]) => Hasher.fromBase64(...args), + }, + { + label: 'From UTF-8', + inputs: ['UTF-8'], + models: [''], + callback: (args: [string]) => Hasher.fromUTF8(...args), + }, + ], + to: [ + { + label: 'To Sha256 Hex', + callback: (hasher: Hasher) => hasher.toSHA256Hex(), + }, + { + label: 'To RipeMD160 Hex', + callback: (hasher: Hasher) => hasher.toRIPEMD160Hex(), + }, + { + label: 'To Hash160 Hex', + callback: (hasher: Hasher) => hasher.toHash160Hex(), + }, + { + label: 'To Hash256 Hex', + callback: (hasher: Hasher) => hasher.toHash256Hex(), + }, + ], +}; + +export const hdPrivateNode = { + from: [ + { + label: 'Generate Random', + inputs: ['Derivation Path'], + models: ["m/44'/145'/0'/0/1"], + callback: () => HDPrivateNode.generateRandom(), + }, + { + label: 'From Mnemonic', + inputs: ['Mnemonic', 'Derivation Path'], + models: ['', "m/44'/145'/0'/0/1"], + callback: (args: [string]) => HDPrivateNode.fromMnemonic(...args), + }, + { + label: 'From XPriv Key', + inputs: ['XPriv Key', 'Derivation Path'], + models: ['', "m/44'/145'/0'/0/1"], + callback: (args: [string]) => HDPrivateNode.fromXPriv(...args), + }, + { + label: 'From Raw', + inputs: ['Raw (LibAuth Format/ExtJSON Encoded)'], + models: [''], + callback: (args: [string]) => + HDPrivateNode.fromRaw(decodeExtendedJson(args.join(''))), + extendedInput: true, + }, + ], + to: [ + { + label: 'To XPriv Key', + callback: (hdPrivateNode: HDPrivateNode) => hdPrivateNode.toXPriv(), + }, + { + label: 'To String', + callback: (hdPrivateNode: HDPrivateNode) => hdPrivateNode.toString(), + }, + { + label: 'To Raw (LibAuth Format/ExtJSON Encoded)', + callback: (hdPrivateNode: HDPrivateNode) => + encodeExtendedJson(hdPrivateNode.toRaw(), 2), + }, + { + label: 'To XPub Key', + callback: (hdPrivateNode: HDPrivateNode) => + hdPrivateNode.deriveHDPublicNode().toXPub(), + }, + ], +}; + +export const hdPublicNode = { + from: [ + { + label: 'From XPub Key', + inputs: ['XPub Key'], + models: [''], + callback: (args: [string]) => HDPublicNode.fromXPub(...args), + }, + { + label: 'From Raw', + inputs: ['Raw (LibAuth Format/ExtJSON Encoded)'], + models: [''], + callback: (args: [string]) => + HDPublicNode.fromRaw(decodeExtendedJson(args.join(''))), + extendedInput: true, + }, + ], + to: [ + { + label: 'To XPub Key', + callback: (hdPublicNode: HDPublicNode) => hdPublicNode.toXPub(), + }, + { + label: 'To String', + callback: (hdPublicNode: HDPublicNode) => hdPublicNode.toString(), + }, + { + label: 'To Raw (LibAuth Format/ExtJSON Encoded)', + callback: (hdPublicNode: HDPublicNode) => + encodeExtendedJson(hdPublicNode.toRaw(), 2), + }, + ], +}; + +export const mnemonic = { + from: [ + { + label: 'Generate Random', + callback: () => Mnemonic.generateRandom(), + }, + { + label: 'From String', + inputs: ['String'], + models: [''], + callback: (args: [string]) => Mnemonic.fromString(...args), + extendedInput: true, + }, + ], + to: [ + { + label: 'To String (without passphrase)', + callback: (mnemonic: Mnemonic) => mnemonic.toString(false), + }, + { + label: 'To String (with passphrase)', + callback: (mnemonic: Mnemonic) => mnemonic.toString(true), + }, + { + label: 'Words', + callback: (mnemonic: Mnemonic) => mnemonic.toWords(), + }, + { + label: 'Passphrase', + callback: (mnemonic: Mnemonic) => mnemonic.getPassphrase(), + }, + ], +}; + +// TODO: Message Signing outputs. Maybe add an `extension` flag to the `to` that allows another input field and uses that to generate the outputs +export const privateKey = { + from: [ + { + label: 'Generate Random', + callback: () => PrivateKey.generateRandom(), + }, + { + label: 'From Hex', + inputs: ['Hex'], + models: [''], + callback: (args: [string]) => PrivateKey.fromHex(...args), + }, + { + label: 'From WIF', + inputs: ['WIF'], + models: [''], + callback: (args: [string]) => PrivateKey.fromWIF(...args), + }, + ], + to: [ + { + label: 'To Hex', + callback: (privateKey: PrivateKey) => privateKey.toHex(), + }, + { + label: 'To WIF', + callback: (privateKey: PrivateKey) => privateKey.toWif(), + }, + { + label: 'To String', + callback: (privateKey: PrivateKey) => privateKey.toString(), + }, + { + label: 'Derived Public Key (Hex)', + callback: (privateKey: PrivateKey) => + privateKey.derivePublicKey().toString(), + }, + { + label: 'Derived BCH Address (CashAddr)', + callback: (privateKey: PrivateKey) => + privateKey.derivePublicKey().deriveAddress().toCashAddr('bitcoincash'), + }, + ], + methods: [ + { + label: 'Sign Message (Schnorr)', + inputs: ['UTF8'], + models: ['Your text here'], + callback: (privateKey: PrivateKey, args: [string]) => + Bytes.fromBytes(privateKey.signMessageSchnorr(...args)).toHex(), + }, + { + label: 'Sign Message (DER)', + inputs: ['UTF8'], + models: ['Your text here'], + callback: (privateKey: PrivateKey, args: [string]) => + Bytes.fromBytes(privateKey.signMessageDER(...args)).toHex(), + }, + ], +}; + +export const publicKey = { + from: [ + { + label: 'From Hex', + inputs: ['Hex'], + models: [''], + callback: (args: [string]) => PublicKey.fromHex(...args), + }, + { + label: 'From WIF', + inputs: ['WIF'], + models: [''], + callback: (args: [string]) => + PrivateKey.fromWIF(...args).derivePublicKey(), + }, + ], + to: [ + { + label: 'Derived Hash160 Hex', + callback: (publicKey: PublicKey) => publicKey.deriveHash160Hex(), + }, + { + label: 'Derived Address', + callback: (publicKey: PublicKey) => + publicKey.deriveAddress().toCashAddr(), + }, + { + label: 'To Hex', + callback: (publicKey: PublicKey) => publicKey.toHex(), + }, + { + label: 'To String', + callback: (publicKey: PublicKey) => publicKey.toString(), + }, + ], + methods: [ + { + label: 'Verify Message (Schnorr)', + inputs: ['Message (UTF8)', 'Signature (Hex)'], + models: ['Your text here'], + callback: (publicKey: PublicKey, args: [string, string]) => + publicKey.verifyMessageSchnorr(...args).toString(), + }, + { + label: 'Verify Message (DER)', + inputs: ['Message (UTF8)', 'Signature (Hex)'], + models: ['Your text here'], + callback: (publicKey: PublicKey, args: [string, string]) => + publicKey.verifyMessageDER(...args).toString(), + }, + ], +}; + +export const satoshis = { + from: [ + { + label: 'From Sats', + inputs: ['Sats'], + models: [''], + callback: (args: [number]) => Satoshis.fromSats(...args), + }, + { + label: 'From Bits', + inputs: ['Bits'], + models: [''], + callback: (args: [number]) => Satoshis.fromBits(...args), + }, + { + label: 'From MBCH', + inputs: ['MBCH'], + models: [''], + callback: (args: [number]) => Satoshis.fromMBCH(...args), + }, + { + label: 'From BCH', + inputs: ['BCH'], + models: [''], + callback: (args: [number]) => Satoshis.fromBCH(...args), + }, + ], + to: [ + { + label: 'To Sats', + callback: (satoshis: Satoshis) => satoshis.toSats().toString(), + }, + { + label: 'To Bits', + callback: (satoshis: Satoshis) => satoshis.toBits().toString(), + }, + { + label: 'To MBCH', + callback: (satoshis: Satoshis) => satoshis.toMBCH().toString(), + }, + { + label: 'To BCH', + callback: (satoshis: Satoshis) => satoshis.toBCH().toString(), + }, + ], +}; + +export const script = { + from: [ + { + label: 'From ASM', + inputs: ['ASM'], + models: [''], + callback: (args: [string]) => Script.fromASM(...args), + }, + { + label: 'From Hex', + inputs: ['Hex'], + models: [''], + callback: (args: [string]) => Script.fromHex(...args), + }, + ], + to: [ + { + label: 'To ASM', + callback: (script: Script) => script.toASM(), + }, + { + label: 'To Hex', + callback: (script: Script) => script.toHex(), + }, + ], +}; + +export const transaction = { + from: [ + { + label: 'From Hex', + inputs: ['Hex'], + models: [''], + callback: (args: [string]) => Transaction.fromHex(...args), + extendedInput: true, + }, + { + label: 'From Raw', + inputs: ['Raw (LibAuth Format/ExtJSON Encoded)'], + models: [''], + callback: (args: [...string[]]) => + Transaction.fromRaw(decodeExtendedJson(args.join(''))), + extendedInput: true, + }, + ], + to: [ + { + label: 'To Transaction Hash Hex', + callback: (transaction: Transaction) => transaction.toHashHex(), + }, + { + label: 'To Raw (LibAuth Format/ExtJSON Encoded)', + callback: (transaction: Transaction) => + encodeExtendedJson(transaction.toRaw(), 2), + }, + { + label: 'To Hex', + callback: (transaction: Transaction) => transaction.toHex(), + }, + { + label: 'Version', + callback: (transaction: Transaction) => + transaction.getVersion().toString(), + }, + { + label: 'Inputs', + callback: (transaction: Transaction) => { + return encodeExtendedJson(transaction.getInputs(), 2); + }, + }, + { + label: 'Outputs', + callback: (transaction: Transaction) => { + return encodeExtendedJson(transaction.getOutputs(), 2); + }, + }, + { + label: 'Locktime', + callback: (transaction: Transaction) => + transaction.getLocktime().toString(), + }, + { + label: 'Is Coinbase', + callback: (transaction: Transaction) => + transaction.isCoinbase().toString(), + }, + ], +}; diff --git a/packages/docs/src/tools/primitives.md b/packages/docs/src/tools/primitives.md new file mode 100644 index 0000000..4f7531e --- /dev/null +++ b/packages/docs/src/tools/primitives.md @@ -0,0 +1,146 @@ + + + + +# Primitive Tools + +## Address +
+

A Utility tool for converting between different address formats.

+ + +
+ +## Block Header +
+

A utility tool for converting between different byte representations.

+ + +
+ +## Bytes +
+

A utility tool for converting between different byte representations.

+ + +
+ +## Hasher +
+

A utility tool for computing various cryptographic hashes commonly used in Bitcoin Cash.

+ + +
+ +## HD Private Node +
+

A utility tool for generating and manipulating HD Private Nodes.

+ + +
+ +## HD Public Node +
+

A utility tool for generating and manipulating HD Public Nodes.

+ + +
+ +## Mnemonic +
+

A utility tool for generating and validating BIP39 Mnemonics. Note that mnemonics will be normalized by removing redundant whitespace and will also be validated against the BIP39 English Word List (other word-lists can be specified via the API)

+ + +
+ +## Private Key +
+

A utility tool for generating and manipulating Private Keys.

+ + +
+ +## Public Key +
+

A utility tool for generating and manipulating Public Keys.

+ + +
+ +## Satoshis +
+

A utility tool for converting between Bitcoin Cash value units.

+ + +
+ +## Script +
+

A utility tool for generating and manipulating Script objects.

+ + +
+ +## Transaction +
+

A utility tool for generating and manipulating Transaction objects.

+ + +
diff --git a/packages/docs/src/tools/stack/utils-functions.ts b/packages/docs/src/tools/stack/utils-functions.ts new file mode 100644 index 0000000..d6f3a5f --- /dev/null +++ b/packages/docs/src/tools/stack/utils-functions.ts @@ -0,0 +1,34 @@ +import { Bytes } from '@xocash/primitives'; + +import { Encryption } from '@xocash/stack'; + +export const encryption = { + from: [ + { + label: 'From Passphrase', + inputs: ['Passphrase'], + models: [''], + callback: (args: [string]) => Encryption.fromPassphrase(...args), + extendedInput: true, + }, + ], + to: [], + methods: [ + { + label: 'Encrypt', + inputs: ['UTF8'], + models: ['Your text here'], + callback: async (encryption: Encryption, args: [string]) => + Bytes.fromBytes(await encryption.encrypt(...args)).toHex(), + }, + { + label: 'Decrypt', + inputs: ['Hex'], + models: ['Encrypted Data (hex)'], + callback: async (encryption: Encryption, args: [string]) => + Bytes.fromBytes( + await encryption.decrypt(Bytes.fromHex(...args).toBytes()), + ).toUtf8(), + }, + ], +}; diff --git a/packages/docs/src/tools/stack/utils.md b/packages/docs/src/tools/stack/utils.md new file mode 100644 index 0000000..0707e01 --- /dev/null +++ b/packages/docs/src/tools/stack/utils.md @@ -0,0 +1,23 @@ + + + + +# Utils + +## Encryption +
+

A Utility tool for converting between different address formats.

+ + +
diff --git a/packages/docs/typedoc.json b/packages/docs/typedoc.json new file mode 100644 index 0000000..26a0b93 --- /dev/null +++ b/packages/docs/typedoc.json @@ -0,0 +1,37 @@ +{ + "entryPointStrategy": "packages", + "entryPoints": ["../primitives/", "../stack/"], + "plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"], + "docsRoot": "./src", + "out": "./src/api", + "sidebar": { + "autoConfiguration": true, + "format": "vitepress", + "pretty": true, + "collapsed": true + }, + "indexFormat": "table", + "parametersFormat": "table", + "interfacePropertiesFormat": "table", + "classPropertiesFormat": "table", + "propertyMembersFormat": "table", + "typeDeclarationFormat": "table", + "enumMembersFormat": "table", + "useCodeBlocks": true, + "sort": ["static-first"], + "exclude": "**/*+(index|.spec|.e2e|.browser|.node|.types).ts", + "excludeInternal": true, + "excludeProtected": true, + "excludePrivate": true, + "excludeReferences": true, + "outputFileStrategy": "modules", + "hideGroupHeadings": true + /* + + "categorizeByGroup": false, + "navigation": { + "includeCategories": false, + "includeGroups": true + } + */ +} diff --git a/packages/json-payment-protocol-v2/README.md b/packages/json-payment-protocol-v2/README.md new file mode 100644 index 0000000..f452657 --- /dev/null +++ b/packages/json-payment-protocol-v2/README.md @@ -0,0 +1,3 @@ +# XO Primitives + +TODO diff --git a/packages/vue/package.json b/packages/json-payment-protocol-v2/package.json similarity index 60% rename from packages/vue/package.json rename to packages/json-payment-protocol-v2/package.json index ebb1be9..21c6474 100644 --- a/packages/vue/package.json +++ b/packages/json-payment-protocol-v2/package.json @@ -1,7 +1,7 @@ { - "name": "@xo/vue", + "name": "@xocash/json-payment-protocol-v2", "version": "0.0.1", - "description": "XO Stack Wallet - Vue", + "description": "JSON Payment Protocol V2", "type": "module", "main": "dist/index.js", "module": "dist/index.js", @@ -14,10 +14,5 @@ "prepublishOnly": "npm run build" }, "author": "", - "license": "ISC", - "dependencies": { - "@xo/primitives": "^0.0.1", - "@xo/wallet": "^0.0.1", - "vue": "^3.4.38" - } + "license": "ISC" } diff --git a/packages/json-payment-protocol-v2/src/index.ts b/packages/json-payment-protocol-v2/src/index.ts new file mode 100644 index 0000000..4be3b33 --- /dev/null +++ b/packages/json-payment-protocol-v2/src/index.ts @@ -0,0 +1,338 @@ +import { + binsAreEqual, + hexToBin, + secp256k1, + sha256, + utf8ToBin, +} from '@bitauth/libauth'; + +export type TrustedKeys = { + [identity: string]: { + owner: string; + expirationDate: string; + domains: Array; + publicKeys: Array; + }; +}; + +export interface PaymentOptionsResponse { + time: string; // ISO date string + expires: string; // ISO date string + memo: string; + paymentUrl: string; + paymentId: string; + paymentOptions: Array<{ + chain: string; + currency: string; + network: string; + estimatedAmount: number; + requiredFeeRate: number; + minerFee: number; + decimals: number; + selected: boolean; + }>; +} + +export interface PaymentRequestBody { + chain: string; + currency?: string; +} + +export interface PaymentRequestResponse { + time: string; // ISO date string + expires: string; // ISO date string + memo: string; + paymentUrl: string; + paymentId: string; + chain: string; + network: string; + instructions: Array<{ + type: string; + requiredFeeRate?: number; + outputs: Array<{ + amount: number; + address: string; + }>; + }>; +} + +export interface PaymentVerificationBody { + chain: string; + transactions: Array<{ + tx: string; + }>; + currency: string; +} + +export interface PaymentBody { + chain: string; + transactions: Array<{ + tx: string; + }>; +} + +export interface PaymentResponse { + memo: string; + transactions: Array<{ + tx: string; + }>; +} + +// These are fetched from https://bitpay.com/signingKeys/paymentProtocol.json +// Note that the expiration date is 2018, so I'm not sure wallets would even be using these anymore. +export const DEFAULT_TRUSTED_KEYS: TrustedKeys = { + mh65MN7drqmwpCRZcEeBEE9ceQCQ95HtZc: { + owner: 'BitPay, Inc.', + expirationDate: '2018-12-04T00:00:00.000Z', + domains: ['test.bitpay.com'], + publicKeys: [ + '03159069584176096f1c89763488b94dbc8d5e1fa7bf91f50b42f4befe4e45295a', + '03de8bc75ff1fd82133dd1777fa08449032d4a7ac58c234545ffb7da38bc94aea1', + ], + }, + '1DbY94wCcLRM1Y6RGFg457JyqBbsYxzfiN': { + owner: 'BitPay, Inc.', + expirationDate: '2018-12-03T00:00:00.000Z', + domains: ['bitpay.com'], + publicKeys: [ + '03218884b9a42334195ec32344d487ef291fda4b6e712a7858e9836c2d326e0c08', + '027ffb2cc626a97bc3b449ae3b28b0acf07bc900bba8912d60ca12ad2b616cbe72', + '03a29de86e138106a329bc6db085f02d8e19f59249204dfd5cc797fcb36b8ed93c', + '022ba5469ec10a1d12911a5ca39c45bd0de24db83145507ffaf1fb6a3ca047f717', + '03065b167ac14111efdbf7708708ecf44d22ed0e97af3c656a183162dabf5a59bf', + '03dc0f18eadf553705ab87421d264204a9fcb8a7984997e07a867cbf55972908eb', + '02cab72d4d5d0c9ede7617562e42c485dcfb34db52fd0e6a3de62462040ac1ce90', + '02e184ad56dee82bbaf290144f24b7ab8e508f6c26e9181c8501c8219a7e1999b1', + '027b7d555a3444936ddb200ca640f41aa8c89851fbbb13521b5aaa32d75df57cc8', + '03c3872f9c42c1c2fca77a77727f3b180cdc811754f6b560d298f1ce63c78df484', + '02527a9029fbb09ec9a02471e03296932d20544d3935fa492e2f74a1f79a0c48f6', + '03adbd79e63b046cc25c70b2c1f428a410e1ca46f88fb6c387f2883090b9fa0367', + '02c9f1fbf8afa5a9b6a22a6c442b184e7e72a8b173c4a06beb11a5b700f024b06e', + '0386126eaa4f7bc816ea0c5841cdd1086704167c126e03c17dbd5fc899469091bb', + '03271e4ea488ed0d1294dce6f3554c5d6f9cb068a847f500f2ea392148402499f1', + ], + }, +}; + +export class JSONPaymentProtocolV2 { + constructor( + public opts: { + verifySignatures: boolean; + allowUnknownIdentities: boolean; + } = { + verifySignatures: false, + allowUnknownIdentities: true, + }, + public trustedKeys: TrustedKeys = DEFAULT_TRUSTED_KEYS, + ) {} + + async paymentOptions(url: string): Promise { + const response = await fetch(url, { + method: 'GET', + headers: { + accept: 'application/payment-options', + 'x-paypro-version': '2', + }, + }); + + if (!response.ok) { + throw new Error(`${(await response.text()) || response.status}`); + } + + const responseRaw = await response.text(); + + if (this.opts.verifySignatures) { + this.verifyResponseSignature(url, responseRaw, response.headers); + } + + return JSON.parse(responseRaw); + } + + async paymentRequest( + url: string, + body: PaymentRequestBody = { + chain: 'BCH', + currency: 'BCH', + }, + ): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/payment-request', + 'x-paypro-version': '2', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`${(await response.text()) || response.status}`); + } + + const responseRaw = await response.text(); + + if (this.opts.verifySignatures) { + this.verifyResponseSignature(url, responseRaw, response.headers); + } + + return JSON.parse(responseRaw); + } + + async paymentVerifcation( + url: string, + body: PaymentVerificationBody, + ): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/payment', + 'x-paypro-version': '2', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`${(await response.text()) || response.status}`); + } + + const responseRaw = await response.text(); + + if (this.opts.verifySignatures) { + this.verifyResponseSignature(url, responseRaw, response.headers); + } + + return JSON.parse(responseRaw); + } + + async payment(url: string, body: PaymentBody): Promise { + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/payment', + 'x-paypro-version': '2', + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error(`${(await response.text()) || response.status}`); + } + + const responseRaw = await response.text(); + + if (this.opts.verifySignatures) { + this.verifyResponseSignature(url, responseRaw, response.headers); + } + + return JSON.parse(responseRaw); + } + + protected verifyResponseSignature( + url: string, + rawBody: string, + headers: Headers, + ): void { + // Parse the URL so we can extract the hostname. + const parsedUrl = new URL(url); + const { hostname } = parsedUrl; + + // Extract necessary headers. + const digest = headers.get('digest'); + const signature = headers.get('signature'); + const signatureType = headers.get('x-signature-type'); + const identity = headers.get('x-identity'); + + // Ensure that expected hash was provided. + if (typeof digest !== 'string' || !digest) { + throw new Error('Invalid digest header provided'); + } + + // Ensure that an identity was provided. + if (typeof identity !== 'string' || !identity) { + throw new Error('Invalid identity header provided.'); + } + + // Ensure that a signature was provided. + if (typeof signature !== 'string' || !signature) { + throw new Error('Invalid signature header provided.'); + } + + // Ensure that signature type is "ecc". + if (typeof signatureType !== 'string' || signatureType !== 'ecc') { + throw new Error( + `Unsupported signature type "${signatureType}" (must be "ecc")`, + ); + } + + // Split the digest into type and content. + const [digestType, digestHash] = digest.split('='); + + // Ensure that digest type is SHA-256. + if (digestType !== 'SHA-256') { + throw new Error( + `Unsupported Digest Type (${digestType}). Must be "SHA-256"`, + ); + } + + // Ensure that digest is present. + if (!digestHash) { + throw new Error('Invalid digest header. Must be of form SHA-256=hash'); + } + + // Try to find a key for this identity in our trusted keys. + const keyData = this.trustedKeys[identity]; + + // If we could not find a key... + if (!keyData) { + // And we do not allow unknown entities, throw an error. + if (!this.opts.allowUnknownIdentities) { + throw new Error(`No keys available for identity "${identity}"`); + } + + // Compile all our known domains into a single array. + const domains = Object.values(this.trustedKeys).flatMap( + (entry) => entry.domains, + ); + + // If this hostname is contained in our known domains, throw an error. + if (domains.includes(hostname)) { + throw new Error( + `"${hostname}" has a trusted key, but it belongs to another identity`, + ); + } + + // Otherwise, just return. + return; + } + + // Ensure that the domain is whitelisted for this identity. + if (!keyData.domains.includes(hostname)) { + throw new Error( + `${hostname} is not included in this identity's trusted domains`, + ); + } + + // Calculate the hash of the body. + const actualHash = sha256.hash(utf8ToBin(rawBody)); + + // Ensure the actual hash matches the expected hash. + if (!binsAreEqual(actualHash, hexToBin(digestHash))) { + throw new Error('Provided digest does not match actual digest'); + } + + // Iterate through each of the whitelisted public keys and see if any of them can match the expected signature. + const didVerifySuccessfully = keyData.publicKeys.some((publicKey) => { + return secp256k1.verifySignatureCompact( + hexToBin(signature), + hexToBin(publicKey), + actualHash, + ); + }); + + // If none of the public keys can verify the signature, throw an error. + if (!didVerifySuccessfully) { + throw new Error( + `Failed to verify the signature against any of the identity's (${identity}) listed public keys`, + ); + } + } +} diff --git a/packages/vue/tsconfig.json b/packages/json-payment-protocol-v2/tsconfig.json similarity index 57% rename from packages/vue/tsconfig.json rename to packages/json-payment-protocol-v2/tsconfig.json index 91b6b51..ed464a9 100644 --- a/packages/vue/tsconfig.json +++ b/packages/json-payment-protocol-v2/tsconfig.json @@ -4,11 +4,6 @@ "outDir": "./dist", "rootDir": "./src" }, - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "dist" - ] + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] } diff --git a/packages/primitives/package.json b/packages/primitives/package.json index e369b4f..278a2b9 100644 --- a/packages/primitives/package.json +++ b/packages/primitives/package.json @@ -1,5 +1,5 @@ { - "name": "@xo/primitives", + "name": "@xocash/primitives", "version": "0.0.1", "description": "XO Stack Primitives", "type": "module", @@ -11,7 +11,10 @@ ], "scripts": { "build": "tsc --build --clean && tsc --build", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test": "vitest", + "test:coverage": "vitest run --coverage", + "watch": "tsc --watch" }, "author": "", "license": "ISC" diff --git a/packages/primitives/src/address.ts b/packages/primitives/src/address.ts index 30115a0..644e2df 100644 --- a/packages/primitives/src/address.ts +++ b/packages/primitives/src/address.ts @@ -28,6 +28,25 @@ export class Address { this.addressContents = addressContents; } + /** + * Decode a Cash Address or Legacy Address. + * + * @param address {string} The address to attempt to decode. + * + * @throws {Error} If address cannot be decoded. + * + * @returns {Address} Instance of Address. + */ + public static from(address: string): Address { + // Attempt to decode as a Cash Address. + try { + return Address.fromCashAddr(address); + } catch (_error) { + // And if that fails, attempt to decode as a Base58 Address. + return Address.fromLegacy(address); + } + } + /** * Decode a CashAddr into an Address Entity. * @@ -40,13 +59,19 @@ export class Address { * @example * const address = Address.fromCashAddr('bitcoincash:qpeqv9k9j3l7fepp9du24f9pr5jdu7c2mvn4wwldav') */ - public static fromCashAddr(cashAddr: string): Address { - // CashAddr - const decodeResult = decodeCashAddress(cashAddr); + public static fromCashAddr( + cashAddr: string, + tryPrefixes: Array = ['', 'bitcoincash', 'bchtest', 'bchreg'], + ): Address { + // Try all prefixes (including empty string) until we find a valid decode + const decodeResult = tryPrefixes + .map((prefix) => + decodeCashAddress(prefix ? `${prefix}:${cashAddr}` : cashAddr), + ) + .find((result) => typeof result !== 'string'); - // If a string is returned, this indicates an error... - if (typeof decodeResult === 'string') { - throw new Error(decodeResult); + if (!decodeResult) { + throw new Error('Invalid address format'); } // Define the encode type in advance. @@ -55,9 +80,11 @@ export class Address { // Determine the encoding to use based on the Decoding Result's Type. switch (decodeResult.type) { case 'p2pkh': + case 'p2pkhWithTokens': encodeType = 'P2PKH'; break; case 'p2sh': + case 'p2shWithTokens': encodeType = decodeResult.payload.length === 32 ? 'P2SH32' : 'P2SH20'; break; default: @@ -106,25 +133,6 @@ export class Address { return new Address({ payload: decodeResult.payload, type: encodeType }); } - /** - * Decode a Cash Address or Legacy Address. - * - * @param address {string} The address to attempt to decode. - * - * @throws {Error} If address cannot be decoded. - * - * @returns {Address} Instance of Address. - */ - public static fromCashAddrOrLegacy(address: string): Address { - // Attempt to decode as a Cash Address. - try { - return Address.fromCashAddr(address); - } catch (error) { - // And if that fails, attempt to decode as a Base58 Address. - return Address.fromLegacy(address); - } - } - /** * Instantiate an address from Contract Bytecode. * @@ -136,7 +144,7 @@ export class Address { */ public static fromRedeemScript( contractBytes: Uint8Array, - type: 'P2SH20' | 'P2SH32' = 'P2SH32' + type: 'P2SH20' | 'P2SH32' = 'P2SH32', ): Address { // Do an initial SHA256 on the Contract Bytes. let payload = sha256.hash(contractBytes); @@ -164,7 +172,7 @@ export class Address { */ public static fromRedeemScriptHex( hex: string, - type: 'P2SH20' | 'P2SH32' = 'P2SH32' + type: 'P2SH20' | 'P2SH32' = 'P2SH32', ): Address { if (!isHex(hex)) { throw new Error('Invalid hex'); @@ -227,12 +235,12 @@ export class Address { */ public static fromHash160( hash160: Uint8Array, - type: AddressType = 'P2PKH' + type: AddressType = 'P2PKH', ): Address { // If the Hash160 bytes is not 20 in length, it is invalid... if (hash160.length !== 20) { throw new Error( - `Invalid hash160 length: Expected 20 (Received ${hash160.length})` + `Invalid hash160 length: Expected 20 (Received ${hash160.length})`, ); } @@ -251,7 +259,7 @@ export class Address { */ public static fromHash160Hex( hash160Hex: string, - type: AddressType = 'P2PKH' + type: AddressType = 'P2PKH', ): Address { if (!isHex(hash160Hex)) { throw new Error('Invalid hex'); @@ -262,10 +270,14 @@ export class Address { return Address.fromHash160(hash160Bytes, type); } + public static fromRaw(addressContents: KnownAddressTypeContents) { + return new this(addressContents); + } + public static isValid(address: string): boolean { try { - Address.fromCashAddrOrLegacy(address); - } catch (error) { + Address.from(address); + } catch (_error) { return false; } @@ -275,7 +287,7 @@ export class Address { public static isValidLegacy(address: string): boolean { try { Address.fromLegacy(address); - } catch (error) { + } catch (_error) { return false; } @@ -285,7 +297,7 @@ export class Address { public static isValidCashAddr(address: string): boolean { try { Address.fromCashAddr(address); - } catch (error) { + } catch (_error) { return false; } @@ -330,25 +342,60 @@ export class Address { */ public toCashAddr( networkPrefix: 'bitcoincash' | 'bchtest' | 'bchreg' = 'bitcoincash', - supportTokens = false ): string { // Define the encode type in advance. - let encodeType: 'p2pkh' | 'p2sh' | 'p2pkhWithTokens' | 'p2shWithTokens'; + let encodeType: 'p2pkh' | 'p2sh'; // Determine the encoding to use based on the Address Content's Type. switch (this.addressContents.type) { case 'P2PKH': - encodeType = supportTokens ? 'p2pkhWithTokens' : 'p2pkh'; + encodeType = 'p2pkh'; break; case 'P2SH20': - encodeType = supportTokens ? 'p2shWithTokens' : 'p2sh'; - break; case 'P2SH32': - encodeType = supportTokens ? 'p2shWithTokens' : 'p2sh'; + encodeType = 'p2sh'; break; default: throw new Error( - `Unsupported address type: ${this.addressContents.type}` + `Unsupported address type: ${this.addressContents.type}`, + ); + } + + // NOTE: This function will throw if the payload is invalid. + return encodeCashAddress({ + prefix: networkPrefix, + type: encodeType, + payload: this.addressContents.payload, + }).address; + } + + /** + * Converts this address to a Tokens Address format. + * + * @param networkPrefix {'bitcoincash' | 'bchtest' | 'bchreg'} The Cash Address Network prefix. + * + * @throws {Error} If address cannot be encoded as a CashAddr. + * + * @returns {string} The address in Token Address format. + */ + public toTokenAddress( + networkPrefix: 'bitcoincash' | 'bchtest' | 'bchreg' = 'bitcoincash', + ): string { + // Define the encode type in advance. + let encodeType: 'p2pkhWithTokens' | 'p2shWithTokens'; + + // Determine the encoding to use based on the Address Content's Type. + switch (this.addressContents.type) { + case 'P2PKH': + encodeType = 'p2pkhWithTokens'; + break; + case 'P2SH20': + case 'P2SH32': + encodeType = 'p2shWithTokens'; + break; + default: + throw new Error( + `Unsupported address type: ${this.addressContents.type}`, ); } @@ -381,7 +428,7 @@ export class Address { break; default: throw new Error( - `Unsupported address type: ${this.addressContents.type}` + `Unsupported address type: ${this.addressContents.type}`, ); } @@ -419,4 +466,8 @@ export class Address { return lockscriptAsm; } + + public toRaw() { + return { ...this.addressContents }; + } } diff --git a/packages/primitives/src/block-header.ts b/packages/primitives/src/block-header.ts new file mode 100644 index 0000000..7558995 --- /dev/null +++ b/packages/primitives/src/block-header.ts @@ -0,0 +1,182 @@ +import { binToHex, hexToBin, isHex, hash256 } from '@bitauth/libauth'; + +export interface BlockHeaderRaw { + version: number; + previousBlockHash: Uint8Array; + merkleRoot: Uint8Array; + timestamp: number; + bits: number; + nonce: number; +} + +/** + * Represents a Bitcoin Cash block header. + * This class handles parsing and serialization of BCH block headers between different formats + * (raw object, bytes, hex) and provides access to individual header fields. + */ +export class BlockHeader { + /** + * Creates a new BlockHeader instance. + * @param version - The block version + * @param previousBlockHash - Hash of the previous block (32 bytes) + * @param merkleRoot - Merkle root of the block's transactions (32 bytes) + * @param timestamp - Block timestamp in seconds since Unix epoch + * @param bits - Compact representation of the target threshold + * @param nonce - Value modified during mining to make the block hash valid + */ + constructor( + private readonly version: number, + private readonly previousBlockHash: Uint8Array, + private readonly merkleRoot: Uint8Array, + private readonly timestamp: number, + private readonly bits: number, + private readonly nonce: number, + ) {} + + /** + * Creates a BlockHeader instance from a byte array. + * @param bytes - 80-byte array containing the serialized block header + * @returns A new BlockHeader instance + * @throws Error if the byte array length is not 80 + */ + static fromBytes(bytes: Uint8Array): BlockHeader { + if (bytes.length !== 80) { + throw new Error('Invalid block header length'); + } + + const view = new DataView(bytes.buffer); + return new BlockHeader( + view.getInt32(0, true), + bytes.slice(4, 36), + bytes.slice(36, 68), + view.getUint32(68, true), + view.getUint32(72, true), + view.getUint32(76, true), + ); + } + + /** + * Creates a BlockHeader instance from a hex string. + * @param hex - 160-character hex string representing the block header + * @returns A new BlockHeader instance + * @throws Error if the hex string length is not 160 + */ + static fromHex(hex: string): BlockHeader { + // Ensure that the given string is hex. + if (!isHex(hex)) { + throw new Error('The given string is not valid hexadecimal.'); + } + + return BlockHeader.fromBytes(hexToBin(hex)); + } + + /** + * Creates a BlockHeader instance from a raw object representation. + * @param obj - Object containing the block header fields + * @returns A new BlockHeader instance + */ + static fromRaw(obj: BlockHeaderRaw): BlockHeader { + return new BlockHeader( + obj.version, + obj.previousBlockHash, + obj.merkleRoot, + obj.timestamp, + obj.bits, + obj.nonce, + ); + } + + /** + * Serializes the block header to a byte array. + * @returns 80-byte array containing the serialized block header + */ + toBytes(): Uint8Array { + const buffer = new ArrayBuffer(80); + const view = new DataView(buffer); + const bytes = new Uint8Array(buffer); + + view.setInt32(0, this.version, true); + bytes.set(this.previousBlockHash, 4); + bytes.set(this.merkleRoot, 36); + view.setUint32(68, this.timestamp, true); + view.setUint32(72, this.bits, true); + view.setUint32(76, this.nonce, true); + + return bytes; + } + + /** + * Converts the block header to a raw object representation. + * @returns Object containing the block header fields + */ + toRaw(): BlockHeaderRaw { + return { + version: this.version, + previousBlockHash: this.previousBlockHash, + merkleRoot: this.merkleRoot, + timestamp: this.timestamp, + bits: this.bits, + nonce: this.nonce, + }; + } + + /** + * Converts the block header to a hex string. + * @returns 160-character hex string representing the block header + */ + toHex(): string { + return binToHex(this.toBytes()); + } + + // Getters + getVersion(): number { + return this.version; + } + getPreviousBlockHash(): Uint8Array { + return this.previousBlockHash; + } + getMerkleRoot(): Uint8Array { + return this.merkleRoot; + } + getTimestamp(): number { + return this.timestamp; + } + getBits(): number { + return this.bits; + } + getNonce(): number { + return this.nonce; + } + + /** + * Gets the previous block hash as a hex string. + * @returns 64-character hex string + */ + getPreviousBlockHashHex(): string { + return binToHex(this.previousBlockHash); + } + + /** + * Gets the merkle root as a hex string. + * @returns 64-character hex string + */ + getMerkleRootHex(): string { + return binToHex(this.merkleRoot); + } + + /** + * Calculates the hash of this block header. + * @returns 32-byte array containing the block hash + */ + getHash(): Uint8Array { + return hash256(this.toBytes()); + } + + /** + * Gets the block hash as a hex string. + * @returns 64-character hex string + */ + getHashHex(): string { + return binToHex(this.getHash()); + } +} diff --git a/packages/primitives/src/bytes.ts b/packages/primitives/src/bytes.ts index d2208b7..3f859ac 100644 --- a/packages/primitives/src/bytes.ts +++ b/packages/primitives/src/bytes.ts @@ -27,6 +27,19 @@ export class Bytes { this.bytes = bytes; } + /** + * Instantiates bytes from an Uint8Array. + * + * @param bytes The bytes as a Uint8Array. + * + * @throws {Error} If Base64 is invalid. + * + * @returns Instance of Bytes. + */ + public static fromBytes(bytes: Uint8Array): Bytes { + return new this(bytes); + } + /** * Instantiates bytes from a Base64 string. * @@ -34,7 +47,7 @@ export class Bytes { * * @throws {Error} If Base64 is invalid. * - * @returns {Bytes} Instance of Bytes. + * @returns Instance of Bytes. */ public static fromBase64(base64: string): Bytes { // If the Base64 is invalid, throw an error. @@ -42,43 +55,43 @@ export class Bytes { throw new Error('Invalid base64 string'); } - return new Bytes(base64ToBin(base64)); + return new this(base64ToBin(base64)); } /** * Instantiates bytes from a BigInt (LE encoded). * - * @param number {number} The bigint number. + * @param number The bigint number. * * @throws {Error} If number is invalid. * - * @returns {Bytes} Instance of Bytes. + * @returns Instance of Bytes. */ public static fromBigInt(number: bigint): Bytes { - return new Bytes(bigIntToBinUintLE(number)); + return new this(bigIntToBinUintLE(number)); } /** * Instantiates bytes from a BigInt (64 Bit LE encoded). * - * @param number {number} The bigint number. + * @param number The bigint number. * * @throws {Error} If number is invalid. * - * @returns {Bytes} Instance of Bytes. + * @returns Instance of Bytes. */ public static fromBigInt64(number: bigint): Bytes { - return new Bytes(bigIntToBinUint64LE(number)); + return new this(bigIntToBinUint64LE(number)); } /** * Instantiates bytes from a hex string. * - * @param hex {string} The hex string. + * @param hex The hex string. * * @throws {Error} If hex is invalid. * - * @returns {Bytes} Instance of Bytes. + * @returns Instance of Bytes. */ public static fromHex(hex: string): Bytes { // If the hex is invalid, throw an error. @@ -86,24 +99,24 @@ export class Bytes { throw new Error('Invalid hexidecimal string'); } - return new Bytes(hexToBin(hex)); + return new this(hexToBin(hex)); } /** * Instantiates bytes from a UTF8 string. * - * @param utf8 {string} The UTF8 string. + * @param utf8 The UTF8 string. * - * @returns {Bytes} Instance of Bytes. + * @returns Instance of Bytes. */ public static fromUtf8(utf8: string): Bytes { - return new Bytes(utf8ToBin(utf8)); + return new this(utf8ToBin(utf8)); } /** * Return bytes as a Base64 string. * - * @returns {string} Bytes as a Base64 string. + * @returns Bytes as a Base64 string. */ public toBase64(): string { return binToBase64(this.bytes); @@ -116,7 +129,7 @@ export class Bytes { /** * Return bytes as a hex string. * - * @returns {string} Bytes as a hex string. + * @returns Bytes as a hex string. */ public toHex(): string { return binToHex(this.bytes); @@ -125,7 +138,7 @@ export class Bytes { /** * Return bytes as a UTF8 string * - * @returns {string} Bytes as a UTF8 string. + * @returns Bytes as a UTF8 string. */ public toUtf8(): string { return binToUtf8(this.bytes); @@ -134,7 +147,7 @@ export class Bytes { /** * Return bytes as a Uint8Array * - * @returns {Uint8Array} Bytes as a Uint8Array. + * @returns Bytes as a Uint8Array. */ public toBytes(): Uint8Array { return this.bytes; diff --git a/packages/primitives/src/crypto.ts b/packages/primitives/src/crypto.ts deleted file mode 100644 index 6d664cc..0000000 --- a/packages/primitives/src/crypto.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { - ripemd160, - sha256, -} from '@bitauth/libauth'; - -/** -* Crypto Module. -*/ -export class Crypto -{ - /** - * SHA256 hash the given input - * - * @param input {Uint8Array} The input to hash. - * - * @returns {Uint8Array} The SHA256 Hash of the given input. - */ - public static sha256(input: Uint8Array): Uint8Array - { - return sha256.hash(input); - } - - /** - * RIPEMD160 hash the given input - * - * @param input {Uint8Array} The input to hash. - * - * @returns {Uint8Array} The RIPEMD160 Hash of the given input. - */ - public static ripemd160(input: Uint8Array): Uint8Array - { - return ripemd160.hash(input); - } - - /** - * Double SHA256 hash the given input - * - * @param input {Uint8Array} The input to hash. - * - * @returns {Uint8Array} The Double SHA256 Hash of the given input. - */ - public static hash256(input: Uint8Array): Uint8Array - { - return sha256.hash(sha256.hash(input)); - } - - /** - * RIPEMD160 and then SHA256 hash the given input - * - * @param input {Uint16Array} The input to hash. - * - * @returns {Uint8Array} The RIPEMD160+SHA256 Hash of the given input. - */ - public static hash160(input: Uint8Array): Uint8Array - { - return ripemd160.hash(sha256.hash(input)); - } -} diff --git a/packages/primitives/src/ext-json.ts b/packages/primitives/src/ext-json.ts new file mode 100644 index 0000000..4775982 --- /dev/null +++ b/packages/primitives/src/ext-json.ts @@ -0,0 +1,122 @@ +// TODO: These are intended as temporary stand-ins until this functionality has been implemented directly in LibAuth. +// We are doing this so that we may better standardize with the rest of the BCH eco-system in future. +// See: https://github.com/bitauth/libauth/pull/108 + +import { binToHex, hexToBin } from '@bitauth/libauth'; + +export const extendedJsonReplacer = function (value: any): any { + if (typeof value === 'bigint') { + return ``; + } else if (value instanceof Uint8Array) { + return ``; + } + + return value; +}; + +export const extendedJsonReviver = function (value: any): any { + // Define RegEx that matches our Extended JSON fields. + const bigIntRegex = /^[+-]?[0-9]*)n>$/; + const uint8ArrayRegex = /^[a-f0-9]*)>$/; + + // Only perform a check if the value is a string. + // NOTE: We can skip all other values as all Extended JSON encoded fields WILL be a string. + if (typeof value === 'string') { + // Check if this value matches an Extended JSON encoded bigint. + const bigintMatch = value.match(bigIntRegex); + if (bigintMatch) { + // Access the named group directly instead of using array indices + const { bigint } = bigintMatch.groups!; + + // Return the value casted to bigint. + return BigInt(bigint); + } + + const uint8ArrayMatch = value.match(uint8ArrayRegex); + if (uint8ArrayMatch) { + // Access the named group directly instead of using array indices + const { hex } = uint8ArrayMatch.groups!; + + // Return the value casted to bigint. + return hexToBin(hex); + } + } + + // Return the original value. + return value; +}; + +export const encodeExtendedJsonObject = function (value: any): any { + // If this is an object type (and it is not null - which is technically an "object")... + // ... and it is not an ArrayBuffer (e.g. Uint8Array) which is also technically an "object... + if ( + typeof value === 'object' && + value !== null && + !ArrayBuffer.isView(value) + ) { + // If this is an array, recursively call this function on each value. + if (Array.isArray(value)) { + return value.map(encodeExtendedJsonObject); + } + + // Declare object to store extended JSON entries. + const encodedObject: any = {}; + + // Iterate through each entry and encode it to extended JSON. + for (const [key, valueToEncode] of Object.entries(value)) { + encodedObject[key] = encodeExtendedJsonObject(valueToEncode); + } + + // Return the extended JSON encoded object. + return encodedObject; + } + + // Return the replaced value. + return extendedJsonReplacer(value); +}; + +export const decodeExtendedJsonObject = function (value: any): any { + // If this is an object type (and it is not null - which is technically an "object")... + // ... and it is not an ArrayBuffer (e.g. Uint8Array) which is also technically an "object... + if ( + typeof value === 'object' && + value !== null && + !ArrayBuffer.isView(value) + ) { + // If this is an array, recursively call this function on each value. + if (Array.isArray(value)) { + return value.map(decodeExtendedJsonObject); + } + + // Declare object to store decoded JSON entries. + const decodedObject: any = {}; + + // Iterate through each entry and decode it from extended JSON. + for (const [key, valueToEncode] of Object.entries(value)) { + decodedObject[key] = decodeExtendedJsonObject(valueToEncode); + } + + // Return the extended JSON encoded object. + return decodedObject; + } + + // Return the revived value. + return extendedJsonReviver(value); +}; + +export const encodeExtendedJson = function ( + value: any, + space: number | undefined = undefined, +): string { + const replacedObject = encodeExtendedJsonObject(value); + const stringifiedObject = JSON.stringify(replacedObject, null, space); + + return stringifiedObject; +}; + +export const decodeExtendedJson = function (json: string): any { + const parsedObject = JSON.parse(json); + const revivedObject = decodeExtendedJsonObject(parsedObject); + + return revivedObject; +}; diff --git a/packages/primitives/src/hasher.ts b/packages/primitives/src/hasher.ts new file mode 100644 index 0000000..2f2989c --- /dev/null +++ b/packages/primitives/src/hasher.ts @@ -0,0 +1,137 @@ +import { + isHex, + isBase64, + binToHex, + base64ToBin, + hexToBin, + utf8ToBin, + ripemd160, + sha256, +} from '@bitauth/libauth'; + +/** + * Represents a hash primitive that provides various cryptographic hashing capabilities. + * This class handles conversions between different formats (bytes, hex, UTF-8) and + * supports multiple hashing algorithms (SHA256, RIPEMD160, Hash160, Hash256). + */ +export class Hasher { + /** + * Creates a new Hash instance. + * @param bytes - The byte array representation of the data to be hashed + */ + constructor(public bytes: Uint8Array) {} + + /** + * Creates a Hash instance from a byte array. + * @param bytes - The byte array to create the hash from + * @returns A new Hash instance containing the provided bytes + */ + public static fromBytes(bytes: Uint8Array) { + return new this(bytes); + } + + /** + * Creates a Hash instance from a Base64 string. + * @param base64 - The Base64 string to create the hash from + * @returns A new Hash instance containing the bytes converted from the Base64 string + * @throws {Error} If the provided string is not valid Base64 + */ + public static fromBase64(base64: string) { + // Ensure that the given string is base64. + if (!isBase64(base64)) { + throw new Error('The given string is not valid Base64.'); + } + + return new this(base64ToBin(base64)); + } + + /** + * Creates a Hash instance from a hexadecimal string. + * @param hex - The hexadecimal string to create the hash from + * @returns A new Hash instance containing the bytes converted from the hex string + * @throws {Error} If the provided string is not valid hexadecimal + */ + public static fromHex(hex: string) { + // Ensure that the given string is hex. + if (!isHex(hex)) { + throw new Error('The given string is not valid hexadecimal.'); + } + + return new this(hexToBin(hex)); + } + + /** + * Creates a Hash instance from a UTF-8 string. + * @param utf8 - The UTF-8 string to create the hash from + * @returns A new Hash instance containing the bytes converted from the UTF-8 string + */ + public static fromUTF8(utf8: string) { + return new this(utf8ToBin(utf8)); + } + + /** + * Computes the SHA256 hash of the stored bytes. + * @returns A Uint8Array containing the SHA256 hash + */ + public toSHA256Bytes(): Uint8Array { + return sha256.hash(this.bytes); + } + + /** + * Computes the SHA256 hash of the stored bytes and returns it as a hexadecimal string. + * @returns The SHA256 hash as a hexadecimal string + */ + public toSHA256Hex(): string { + return binToHex(this.toSHA256Bytes()); + } + + /** + * Computes the RIPEMD160 hash of the stored bytes. + * @returns A Uint8Array containing the RIPEMD160 hash + */ + public toRIPEMD160Bytes(): Uint8Array { + return ripemd160.hash(this.bytes); + } + + /** + * Computes the RIPEMD160 hash of the stored bytes and returns it as a hexadecimal string. + * @returns The RIPEMD160 hash as a hexadecimal string + */ + public toRIPEMD160Hex(): string { + return binToHex(this.toRIPEMD160Bytes()); + } + + /** + * Computes the Hash160 (RIPEMD160(SHA256(x))) of the stored bytes. + * This is commonly used in cryptocurrency addresses. + * @returns A Uint8Array containing the Hash160 value + */ + public toHash160Bytes(): Uint8Array { + return ripemd160.hash(this.toSHA256Bytes()); + } + + /** + * Computes the Hash160 (RIPEMD160(SHA256(x))) of the stored bytes and returns it as a hexadecimal string. + * @returns The Hash160 value as a hexadecimal string + */ + public toHash160Hex(): string { + return binToHex(this.toHash160Bytes()); + } + + /** + * Computes the Hash256 (SHA256(SHA256(x))) of the stored bytes. + * This is commonly used for computing block hashes in Bitcoin. + * @returns A Uint8Array containing the Hash256 value + */ + public toHash256Bytes(): Uint8Array { + return sha256.hash(this.toSHA256Bytes()); + } + + /** + * Computes the Hash256 (SHA256(SHA256(x))) of the stored bytes and returns it as a hexadecimal string. + * @returns The Hash256 value as a hexadecimal string + */ + public toHash256Hex(): string { + return binToHex(sha256.hash(this.toSHA256Bytes())); + } +} diff --git a/packages/primitives/src/hd-private-node.ts b/packages/primitives/src/hd-private-node.ts index 19fb634..060eb19 100644 --- a/packages/primitives/src/hd-private-node.ts +++ b/packages/primitives/src/hd-private-node.ts @@ -5,6 +5,7 @@ import { encodeHdPrivateKey, decodeHdPrivateKey, deriveHdPath, + deriveHdPathRelative, deriveHdPublicNode, deriveHdPrivateNodeFromSeed, deriveSeedFromBip39Mnemonic, @@ -40,7 +41,21 @@ export class HDPrivateNode { * * @throws {Error} If HD Private Node cannot be created. * - * @returns {HDPrivateNode} The created HD Private Node. + * @returns The created HD Private Node. + */ + public static fromBytes(seed: Uint8Array): HDPrivateNode { + // Return a new HD Private Node from the given seed. + return this.fromSeed(seed); + } + + /** + * Creates a HD Private Node from a raw seed. + * + * @param seed The raw seed bytes. + * + * @throws {Error} If HD Private Node cannot be created. + * + * @returns The created HD Private Node. */ public static fromSeed(seed: Uint8Array): HDPrivateNode { // If the seed is not 32 bytes, throw an error. @@ -59,7 +74,7 @@ export class HDPrivateNode { } // Return a new HD Private Node from the given seed. - return new HDPrivateNode(deriveResult); + return new this(deriveResult); } /** @@ -69,14 +84,14 @@ export class HDPrivateNode { * * @throws {Error} If HD Private Node cannot be created. * - * @returns {HDPrivateNode} The created HD Private Node. + * @returns The created HD Private Node. */ public static fromMnemonic(mnemonic: string): HDPrivateNode { // Derive the seed from the mnemonic. const seed = deriveSeedFromBip39Mnemonic(mnemonic); // Return a new HDPrivateNode from the given seed. - return HDPrivateNode.fromSeed(seed); + return this.fromSeed(seed); } /** @@ -86,7 +101,7 @@ export class HDPrivateNode { * * @throws {Error} If HD Private Node cannot be created. * - * @returns {HDPrivateNode} The created HD Private Node. + * @returns The created HD Private Node. */ public static fromXPriv(xpriv: string): HDPrivateNode { // Attempt to decode the XPriv Key. @@ -98,7 +113,18 @@ export class HDPrivateNode { } // Return a new HD Private Node from the given XPriv. - return new HDPrivateNode(decodeResult.node); + return new this(decodeResult.node); + } + + /** + * Creates a HD Private Node from raw node data. + * + * @param node The raw node data. + * + * @returns The created HD Private Node. + */ + public static fromRaw(node: HdPrivateNodeValid) { + return new this(node); } /** @@ -106,7 +132,7 @@ export class HDPrivateNode { * * @throws {Error} If HD Private Node cannot be created. * - * @returns {HDPrivateNode} The created HD Private Node. + * @returns The created HD Private Node. */ public static generateRandom(): HDPrivateNode { // Attempt to derive HD Private Node from randomly generated bytes. @@ -118,23 +144,29 @@ export class HDPrivateNode { } // Return the new HD Private Node from the random bytes. - return new HDPrivateNode(deriveResult); + return new this(deriveResult); } /** - * Returns the XPriv string for this node. + * Checks if the given XPriv string is a valid HD Private Key. * - * @returns {string} The XPriv string for this node. + * @param xpriv The XPriv string to check. + * + * @returns True if the XPriv string is valid, false otherwise. */ - public toString(): string { - // Return the Mainnet XPriv. - return this.toXPriv(); + public static isXPriv(xpriv: string): boolean { + try { + this.fromXPriv(xpriv); + return true; + } catch (_error) { + return false; + } } /** * Gets the Private Key entity for this node. * - * @returns {PrivateKey} The Private Key Entity for this node. + * @returns The Private Key Entity for this node. */ public privateKey(): PrivateKey { // Return a Private Key Entity from the private key of our node. @@ -148,7 +180,7 @@ export class HDPrivateNode { * * @throws {Error} If HD Private Node cannot be derived. * - * @returns {HDPrivateNode} The derived HD Private Node + * @returns The derived HD Private Node */ public derivePath(path: string): HDPrivateNode { // Attempt to derive the given path. @@ -163,10 +195,32 @@ export class HDPrivateNode { return new HDPrivateNode(derivePathResult); } + /** + * Derives a HD Private Node from the given relative BIP32 path. + * + * @param path {string} The BIP32 Path (e.g. 0/0). + * + * @throws {Error} If HD Private Node cannot be derived. + * + * @returns The derived HD Private Node + */ + public deriveRelativePath(path: string): HDPrivateNode { + // Attempt to derive the given path. + const derivePathResult = deriveHdPathRelative(this.node, path); + + // If a string is returned, this indicates an error... + if (typeof derivePathResult === 'string') { + throw new Error(derivePathResult); + } + + // Return a new HD Private Node Entity derived from the given path. + return new HDPrivateNode(derivePathResult); + } + /** * Derives a HD Public Node from this Private Node. * - * @returns {HDPublicNode} The derived HD Public Node + * @returns The derived HD Public Node */ public deriveHDPublicNode(): HDPublicNode { // Derive a HD Public Node from this HD Private Node. @@ -176,12 +230,22 @@ export class HDPrivateNode { return new HDPublicNode(publicNode); } + /** + * Returns the Private Key of this node (as a Uint8Array). + * + * @returns The Node's Private Key (as a Uint8Array). + */ + public toBytes(): Uint8Array { + // Encode the XPriv from our node info. + return this.node.privateKey; + } + /** * Converts this node to an XPriv Key for export. * * @param network {HdKeyNetwork} The network to encode for. * - * @returns {string} The XPriv Key. + * @returns The XPriv Key. */ public toXPriv(network: HdKeyNetwork = 'mainnet'): string { // Create our node info structure. @@ -193,4 +257,23 @@ export class HDPrivateNode { // Encode the XPriv from our node info. return encodeHdPrivateKey(nodeInfo).hdPrivateKey; } + + /** + * Returns the XPriv string for this node. + * + * @returns The XPriv string for this node. + */ + public toString(): string { + // Return the Mainnet XPriv. + return this.toXPriv(); + } + + /** + * Returns the raw node data. + * + * @returns The raw node data. + */ + public toRaw(): HdPrivateNodeValid { + return this.node; + } } diff --git a/packages/primitives/src/hd-public-node.ts b/packages/primitives/src/hd-public-node.ts index c35db3d..bebeeb2 100644 --- a/packages/primitives/src/hd-public-node.ts +++ b/packages/primitives/src/hd-public-node.ts @@ -50,20 +50,23 @@ export class HDPublicNode { return new HDPublicNode(decodeResult.node); } - /** - * Returns the XPub string for this node. - * - * @returns {string} The XPub string for this node. - */ - public toString(): string { - // Return the Mainnet XPub. - return this.toXPub(); + public static fromRaw(node: HdPublicNodeValid) { + return new this(node); + } + + public static isXPub(xpub: string): boolean { + try { + this.fromXPub(xpub); + return true; + } catch (_error) { + return false; + } } /** * Gets the Public Key Entity for this node. * - * @returns {PublicKey} The Public Key Entity for this node. + * @returns The Public Key Entity for this node. */ public publicKey(): PublicKey { // Return a Public Key Entity from the public key of our node. @@ -73,11 +76,11 @@ export class HDPublicNode { /** * Derives a HD Public Node from the given BIP32 path. * - * @param path {string} The BIP32 Path (e.g. m/44'/145'/0'/0/1). + * @param path The BIP32 Path (e.g. m/44'/145'/0'/0/1). * * @throws {Error} If HD Public Node cannot be derived. * - * @returns {HDPrivateNode} The derived HD Public Node + * @returns The derived HD Public Node */ public derivePath(path: string): HDPublicNode { // Attempt to derive the given path. @@ -95,9 +98,9 @@ export class HDPublicNode { /** * Converts this node to an XPub Key for export. * - * @param network {HdKeyNetwork} The network to encode for. + * @param network The network to encode for. * - * @returns {string} The XPub Key. + * @returns The XPub Key. */ public toXPub(network: HdKeyNetwork = 'mainnet'): string { // Create our node info structure. @@ -109,4 +112,18 @@ export class HDPublicNode { // Encode the XPub from our node info. return encodeHdPublicKey(nodeInfo).hdPublicKey; } + + public toRaw(): HdPublicNodeValid { + return this.node; + } + + /** + * Returns the XPub string for this node. + * + * @returns {string} The XPub string for this node. + */ + public toString(): string { + // Return the Mainnet XPub. + return this.toXPub(); + } } diff --git a/packages/primitives/src/index.ts b/packages/primitives/src/index.ts index 5f987d8..a8b858b 100644 --- a/packages/primitives/src/index.ts +++ b/packages/primitives/src/index.ts @@ -1,11 +1,16 @@ export * from './address.js'; +export * from './block-header.js'; export * from './bytes.js'; -export * from './crypto.js'; +export * from './ext-json.js'; +export * from './hasher.js'; export * from './hd-private-node.js'; export * from './hd-public-node.js'; +export * from './mnemonic.js'; export * from './private-key.js'; export * from './public-key.js'; export * from './satoshis.js'; export * from './script.js'; export * from './transaction.js'; export * from './transaction-hash.js'; +export * from './utils.js'; +export * from './utxo.js'; diff --git a/packages/primitives/src/mnemonic.ts b/packages/primitives/src/mnemonic.ts new file mode 100644 index 0000000..3717896 --- /dev/null +++ b/packages/primitives/src/mnemonic.ts @@ -0,0 +1,246 @@ +import { + generateBip39MnemonicNonStandard, + bip39WordListEnglish, +} from '@bitauth/libauth'; + +export type MnemonicRaw = { + words: string[]; + passphrase?: string; +} + +/** + * Class representing a BIP39 Mnemonic phrase. + * Supports 12, 15, 18, 21, and 24 word phrases with optional passphrase. + */ +export class Mnemonic { + public static VALID_WORD_COUNTS = new Set([12, 15, 18, 21, 24]); + + /** + * Instantiate Mnemonic from a phrase string. + * + * @param phrase The complete phrase (mnemonic words + optional passphrase). + * @param wordList Optional wordlist (defaults to English BIP-39 wordlist). + * @throws {Error} If the mnemonic is invalid. + */ + constructor(private mnemonic: MnemonicRaw) {} + + /** + * Instantiates a mnemonic from a string or array of words + * + * @param phraseOrWords The mnemonic phrase as a string or array of words + * @param dictionary Optional wordlist (defaults to English BIP-39 wordlist). + * @throws {Error} If mnemonic could not be generated. + * @returns Instance of Mnemonic. + */ + public static from(phraseOrWords: string | Array, wordList: Array = bip39WordListEnglish) { + // If this is a list of words... + if (Array.isArray(phraseOrWords)) { + return this.fromWords(phraseOrWords, wordList); + } + + return this.fromString(phraseOrWords, wordList); + } + + /** + * Instantiates a mnemonic from a string + * + * @param phrase The mnemonic phrase as a string + * @param dictionary Optional wordlist (defaults to English BIP-39 wordlist). + * @throws {Error} If mnemonic could not be generated. + * @returns Instance of Mnemonic. + */ + public static fromString(phrase: string, wordList: Array = bip39WordListEnglish) { + // Split into words and remove any whitespace. + const allWords = phrase + .trim() + .split(/\s+/) + .map((word) => word.trim()); + + return this.fromWords(allWords, wordList); + } + + /** + * Instantiates a mnemonic from an array of words. + * + * @param words Array of words (strings) that represent the mnemonic + * @param dictionary Optional wordlist (defaults to English BIP-39 wordlist). + * @throws {Error} If mnemonic could not be generated. + * @returns Instance of Mnemonic. + */ + public static fromWords(words: Array, wordList: Array = bip39WordListEnglish) { + // Check if last word is a passphrase by looking at total count + const hasPassphrase = + words.length === 13 || + words.length === 16 || + words.length === 19 || + words.length === 22 || + words.length === 25; + + // Parse words and optional passphrase + const mnemonicRaw: MnemonicRaw = { + words: hasPassphrase ? words.slice(0, -1) : words, + } + + if(hasPassphrase) { + mnemonicRaw.passphrase = words[words.length - 1] + } + + // Validate word count + if (!Mnemonic.VALID_WORD_COUNTS.has(mnemonicRaw.words.length)) { + throw new Error( + `Invalid word count ${mnemonicRaw.words.length}. BIP-39 requires 12, 15, 18, 21, or 24 words` + + `${hasPassphrase ? ' (excluding passphrase)' : ''}`, + ); + } + + // Normalize and validate words against dictionary + const wordSet = new Set(wordList); + + // Convert all words to lower-case. + mnemonicRaw.words = mnemonicRaw.words.map((word) => word.toLowerCase()); + + // Find any words that are not present in our dictionary (i.e. invalid words). + const invalidWords = mnemonicRaw.words.reduce((invalidWords, word) => { + if (!wordSet.has(word)) { + invalidWords.push(word); + } + return invalidWords; + }, [] as Array); + + // If there are invalid words that are not present in the provided dictionary, throw an error. + if (invalidWords.length) { + throw new Error( + `The following words are not valid with the provided dictionary: ${invalidWords.join(' ')}`, + ); + } + + return new this(mnemonicRaw); + } + + /** + * Instantiates a mnemonic from its raw (serialized) mnemonic representation. + * + * @remarks this method is unsafe and should only use trusted data. + * + * @param mnemonicRaw The raw menmonic data. + * @throws {Error} If mnemonic could not be generated. + * @returns Instance of Mnemonic. + */ + public static fromRaw(mnemonicRaw: MnemonicRaw) { + return new this(mnemonicRaw); + } + + /** + * Generates a random mnemonic phrase. + * + * @param dictionary Optional wordlist (defaults to English BIP-39 wordlist). + * @throws {Error} If mnemonic could not be generated. + * @returns Instance of Mnemonic. + */ + public static generateRandom( + passphrase?: string, + dictionary: string[] = bip39WordListEnglish, + ): Mnemonic { + // Attempt to generate mnemonic. + const mnemonicResult = generateBip39MnemonicNonStandard(dictionary); + if (typeof mnemonicResult === 'string') { + throw new Error(mnemonicResult); + } + + // Turn phrase parts (phrase) into an array so that we can append the passphrase later (if present). + const words = mnemonicResult.phrase.split(/\s+/); + + // If there is a passphrase, append it. + if (passphrase) { + words.push(passphrase); + } + + // Return the generated phrase (with passphrase appended - if present). + return this.fromWords(words, dictionary); + } + + /** + * Validates a mnemonic phrase. + * + * @param phraseOrWords The mnemonic phrase or words to validate. + * @param wordList Optional wordlist (defaults to English BIP-39 wordlist). + * @returns boolean indicating if the phrase is valid. + */ + public static isValid( + phraseOrWords: string | Array, + wordList: string[] = bip39WordListEnglish, + ): boolean { + try { + this.from(phraseOrWords, wordList); + return true; + } catch { + return false; + } + } + + /** + * Checks if a word exists in the provided dictionary. + * + * @param word Word to check. + * @param dictionary Optional wordlist (defaults to English BIP-39 wordlist). + * @returns boolean indicating if the word is valid. + */ + public static isValidWord( + word: string, + dictionary: string[] = bip39WordListEnglish, + ): boolean { + return dictionary.includes(word.toLowerCase()); + } + + /** + * Gets the passphrase if one exists. + * + * @returns The passphrase or undefined if none exists. + */ + public getPassphrase(): string | undefined { + return this.mnemonic.passphrase; + } + + /** + * Returns the complete phrase as a string. + * + * @param includePassphrase Whether to include the passphrase in the output. + * @returns Complete phrase as a string. + */ + public toString(includePassphrase: boolean = true): string { + // Join the menmonic words, separating them using a space character. + const mnemonicWords = this.mnemonic.words.join(' '); + + // If we should include the passphrase - and a passphrase is present... + if (includePassphrase && this.mnemonic.passphrase) { + return `${mnemonicWords} ${this.mnemonic.passphrase}`; + } + + // Return the words. + return mnemonicWords; + } + + /** + * Gets the number of words in the mnemonic (excluding passphrase). + * + * @returns Number of words. + */ + public toWords(includePassphrase: boolean = true): Array { + const words = [ ...this.mnemonic.words ]; + + if(this.mnemonic.passphrase && includePassphrase) { + words.push(this.mnemonic.passphrase); + } + + return words; + } + + /** + * Returns a serializable represntation of the class + * + * @returns A serializable reprentation of the class. + */ + public toRaw(): MnemonicRaw { + return { ...this.mnemonic }; + } +} diff --git a/packages/primitives/src/private-key.ts b/packages/primitives/src/private-key.ts index e839a27..5ca508c 100644 --- a/packages/primitives/src/private-key.ts +++ b/packages/primitives/src/private-key.ts @@ -7,7 +7,6 @@ import { generatePrivateKey, hexToBin, isHex, - binToUtf8, utf8ToBin, bigIntToCompactUint, secp256k1, @@ -37,7 +36,7 @@ export class PrivateKey { // Ensure that the Private Key is valid. if (!secp256k1.validatePrivateKey(bytes)) { throw new Error( - `Private Key is invalid: Must be >= 0x01 and <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140.` + `Private Key is invalid: Must be >= 0x01 and <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140.`, ); } @@ -70,7 +69,7 @@ export class PrivateKey { // Ensure that the given string is hex. if (!isHex(privateKeyHex)) { throw new Error( - `The given string (${privateKeyHex}) is not valid hexadecimal.` + `The given string (${privateKeyHex}) is not valid hexadecimal.`, ); } @@ -90,7 +89,7 @@ export class PrivateKey { */ public static fromWIF( wif: string, - removePrefixes = ['bitcoincash:', 'wif:'] + removePrefixes = ['bitcoincash:', 'wif:'], ): PrivateKey { // Remove the prefixes from the WIF (if they exist). const wifWithoutPrefixes = removePrefixes.reduce((result, prefix) => { @@ -105,6 +104,22 @@ export class PrivateKey { throw new Error(decodeResult); } + // Ensure the Private Key is valid by checking the length (32 bytes = uncompressed private key, 33 bytes = compressed private key). + // NOTE: This is to work around a LibAuth bug where decodePrivateKeyWif does not verify the version of the Base58 string. + // This means that we will get a false positive on Base58/Legacy addresses as being valid WIFs. + // Once the below PR is merged into LibAuth (and we've updated to the new version), we can remove the following code. + // https://github.com/bitauth/libauth/pull/147 + const isValidPrivateKey = + decodeResult.privateKey.length === 32 || + decodeResult.privateKey.length === 33; + + // If it is not a valid private key, then it is not a WIF, + if (!isValidPrivateKey) { + throw new Error( + `Expected 32 or 33 bytes WIF, received ${decodeResult.privateKey.length}`, + ); + } + // Return a new Private Key entity. return new PrivateKey(decodeResult.privateKey); } @@ -120,6 +135,15 @@ export class PrivateKey { return new PrivateKey(bytes); } + public static isWIF(wif: string, removePrefixes = ['bitcoincash:', 'wif:']) { + try { + this.fromWIF(wif, removePrefixes); + return true; + } catch (_error) { + return false; + } + } + /** * Derive the public key from this private key. * @@ -141,101 +165,83 @@ export class PrivateKey { } /** - * Sign a message hash with this Private Key using a Recoverable Compact Signature. + * Sign a message using a recoverable compact signature format. * - * @param messageHash {Uint8Array} The hash of the message. + * @param message The message to sign, either as a Uint8Array or string. + * If a string is provided, it will be converted to UTF-8 bytes. * - * @throws {Error} If message hash cannot be signed. + * @throws {Error} If message cannot be signed. * - * @returns {Uint8Array} The generated signature. + * @returns The generated recoverable compact signature. */ - public signMessageHashRecoverableCompact( - messageHash: Uint8Array - ): RecoverableSignature { - // Attempt to sign the message. - const signResult = secp256k1.signMessageHashRecoverableCompact( - this.bytes, - messageHash - ); - - // If a string is returned, this indicates an error... - if (typeof signResult === 'string') { - throw new Error(signResult); + public signMessageRecoverableCompact(message: Uint8Array | string) { + if (typeof message === 'string') { + message = utf8ToBin(message); } - // Return the signature. - return signResult; + const messageHash = sha256.hash(message); + + return this.signMessageHashRecoverableCompact(messageHash); } /** - * Sign a message hash with this Private Key using a Compact Signature. + * Sign a message using a compact signature format. * - * @param messageHash {Uint8Array} The hash of the message. + * @param message The message to sign, either as a Uint8Array or string. + * If a string is provided, it will be converted to UTF-8 bytes. * - * @throws {Error} If message hash cannot be signed. + * @throws {Error} If message cannot be signed. * - * @returns {Uint8Array} The generated signature. + * @returns The generated compact signature. */ - public signMessageHashCompact(messageHash: Uint8Array): Uint8Array { - // Attempt to sign the message. - const signResult = secp256k1.signMessageHashCompact( - this.bytes, - messageHash - ); - - // If a string is returned, this indicates an error... - if (typeof signResult === 'string') { - throw new Error(signResult); + public signMessageCompact(message: Uint8Array | string) { + if (typeof message === 'string') { + message = utf8ToBin(message); } - // Return the signature. - return signResult; + const messageHash = sha256.hash(message); + + return this.signMessageHashCompact(messageHash); } /** - * Sign a message hash with this Private Key using a Schnorr Signature. + * Sign a message using the Schnorr signature scheme. * - * @param messageHash {Uint8Array} The hash of the message. + * @param message The message to sign, either as a Uint8Array or string. + * If a string is provided, it will be converted to UTF-8 bytes. * - * @throws {Error} If message hash cannot be signed. + * @throws {Error} If message cannot be signed. * - * @returns {Uint8Array} The generated signature. + * @returns The generated Schnorr signature. */ - public signMessageHashSchnorr(messageHash: Uint8Array): Uint8Array { - // Attempt to sign the message. - const signResult = secp256k1.signMessageHashSchnorr( - this.bytes, - messageHash - ); - - // If a string is returned, this indicates an error... - if (typeof signResult === 'string') { - throw new Error(signResult); + public signMessageSchnorr(message: Uint8Array | string) { + if (typeof message === 'string') { + message = utf8ToBin(message); } - // Return the signature. - return signResult; + const messageHash = sha256.hash(message); + + return this.signMessageHashSchnorr(messageHash); } /** - * Sign a message hash with this Private Key using a DER Signature. + * Sign a message using DER (Distinguished Encoding Rules) format. * - * @param messageHash {Uint8Array} The hash of the message. + * @param message The message to sign, either as a Uint8Array or string. + * If a string is provided, it will be converted to UTF-8 bytes. * - * @throws {Error} If message hash cannot be signed. + * @throws {Error} If message cannot be signed. * - * @returns {Uint8Array} The generated signature. + * @returns The generated DER signature. */ - public signMessageHashDER(messageHash: Uint8Array): Uint8Array { - const signResult = secp256k1.signMessageHashDER(this.bytes, messageHash); - - // If a string is returned, this indicates an error... - if (typeof signResult === 'string') { - throw new Error(signResult); + public signMessageDER(message: Uint8Array | string) { + if (typeof message === 'string') { + message = utf8ToBin(message); } - // Return the signature. - return signResult; + const messageHash = sha256.hash(message); + + return this.signMessageHashDER(messageHash); } /** @@ -279,6 +285,128 @@ export class PrivateKey { return electronEncoding; } + /** + * Sign a message hash with this Private Key using a Recoverable Compact Signature. + * + * @param messageHash {Uint8Array} The hash of the message. + * + * @throws {Error} If message hash cannot be signed. + * + * @returns {Uint8Array} The generated signature. + */ + public signMessageHashRecoverableCompact( + messageHash: Uint8Array | string, + ): RecoverableSignature { + // If a string was given, convert it from hex to bytes. + // TODO: Consider using Bytes class - it ensures valid hex. + if (typeof messageHash === 'string') { + messageHash = hexToBin(messageHash); + } + + // Attempt to sign the message. + const signResult = secp256k1.signMessageHashRecoverableCompact( + this.bytes, + messageHash, + ); + + // If a string is returned, this indicates an error... + if (typeof signResult === 'string') { + throw new Error(signResult); + } + + // Return the signature. + return signResult; + } + + /** + * Sign a message hash with this Private Key using a Compact Signature. + * + * @param messageHash {Uint8Array} The hash of the message. + * + * @throws {Error} If message hash cannot be signed. + * + * @returns {Uint8Array} The generated signature. + */ + public signMessageHashCompact(messageHash: Uint8Array | string): Uint8Array { + // If a string was given, convert it from hex to bytes. + // TODO: Consider using Bytes class - it ensures valid hex. + if (typeof messageHash === 'string') { + messageHash = hexToBin(messageHash); + } + + // Attempt to sign the message. + const signResult = secp256k1.signMessageHashCompact( + this.bytes, + messageHash, + ); + + // If a string is returned, this indicates an error... + if (typeof signResult === 'string') { + throw new Error(signResult); + } + + // Return the signature. + return signResult; + } + + /** + * Sign a message hash with this Private Key using a Schnorr Signature. + * + * @param messageHash {Uint8Array} The hash of the message. + * + * @throws {Error} If message hash cannot be signed. + * + * @returns {Uint8Array} The generated signature. + */ + public signMessageHashSchnorr(messageHash: Uint8Array | string): Uint8Array { + // If a string was given, convert it from hex to bytes. + // TODO: Consider using Bytes class - it ensures valid hex. + if (typeof messageHash === 'string') { + messageHash = hexToBin(messageHash); + } + + // Attempt to sign the message. + const signResult = secp256k1.signMessageHashSchnorr( + this.bytes, + messageHash, + ); + + // If a string is returned, this indicates an error... + if (typeof signResult === 'string') { + throw new Error(signResult); + } + + // Return the signature. + return signResult; + } + + /** + * Sign a message hash with this Private Key using a DER Signature. + * + * @param messageHash {Uint8Array} The hash of the message. + * + * @throws {Error} If message hash cannot be signed. + * + * @returns {Uint8Array} The generated signature. + */ + public signMessageHashDER(messageHash: Uint8Array | string): Uint8Array { + // If a string was given, convert it from hex to bytes. + // TODO: Consider using Bytes class - it ensures valid hex. + if (typeof messageHash === 'string') { + messageHash = hexToBin(messageHash); + } + + const signResult = secp256k1.signMessageHashDER(this.bytes, messageHash); + + // If a string is returned, this indicates an error... + if (typeof signResult === 'string') { + throw new Error(signResult); + } + + // Return the signature. + return signResult; + } + /** * Convert the raw bytes of this Private Key to hexadecimal encoding. * diff --git a/packages/primitives/src/public-key.ts b/packages/primitives/src/public-key.ts index 5e14a70..899de7b 100644 --- a/packages/primitives/src/public-key.ts +++ b/packages/primitives/src/public-key.ts @@ -7,6 +7,7 @@ import { secp256k1, sha256, ripemd160, + utf8ToBin, } from '@bitauth/libauth'; /** @@ -19,7 +20,7 @@ export class PublicKey { /** * Construct a new Public Key. * - * @param bytes {Uint8Array} The raw bytes (33) for the Public Key. + * @param bytes The raw bytes (33) for the Public Key. */ constructor(bytes: Uint8Array) { // Ensure that exactly 33 Bytes are given. @@ -33,17 +34,30 @@ export class PublicKey { /** * Create a Public Key entity from the given hexadecimal string. * - * @param publicKeyHex {string} Hexadecimal encoding of the Public Key bytes. + * @param publicKeyBytes Uint8Array containing the Public Key bytes. * * @throws {Error} If Public Key cannot be created. * - * @returns {PublicKey} The created Public Key entity. + * @returns The created Public Key entity. + */ + public static fromBytes(publicKeyBytes: Uint8Array): PublicKey { + return new PublicKey(publicKeyBytes); + } + + /** + * Create a Public Key entity from the given hexadecimal string. + * + * @param publicKeyHex Hexadecimal encoding of the Public Key bytes. + * + * @throws {Error} If Public Key cannot be created. + * + * @returns The created Public Key entity. */ public static fromHex(publicKeyHex: string): PublicKey { // Ensure that the given string is hex. if (!isHex(publicKeyHex)) { throw new Error( - `The given string (${publicKeyHex}) is not valid hexadecimal.` + `The given string (${publicKeyHex}) is not valid hexadecimal.`, ); } @@ -57,7 +71,7 @@ export class PublicKey { /** * Derive the Hash160 for this Public Key. * - * @returns {Uint8Array} The Hash160 of the Public Key. + * @returns The Hash160 of the Public Key. */ public deriveHash160(): Uint8Array { // SHA256 Hash the public key. @@ -73,7 +87,7 @@ export class PublicKey { /** * Derive the Hash160 as Hex for this Public Key. * - * @returns {string} The Hash160 of the Public Key as hex. + * @returns The Hash160 of the Public Key as hex. */ public deriveHash160Hex(): string { const hash160Bytes = this.deriveHash160(); @@ -84,7 +98,7 @@ export class PublicKey { /** * Derive the Address for this Public Key. * - * @returns {Address} The Address for this Public Key. + * @returns The Address for this Public Key. */ public deriveAddress(): Address { // Get the RIPEMD160 Hash of the Public Key. @@ -94,22 +108,48 @@ export class PublicKey { return Address.fromHash160(ripemd160Hash, 'P2PKH'); } + public verifyMessageSchnorr( + message: Uint8Array | string, + signature: Uint8Array | string, + ) { + if (typeof message === 'string') { + message = utf8ToBin(message); + } + + const messageHash = sha256.hash(message); + + return this.verifyMessageHashSchnorr(messageHash, signature); + } + + public verifyMessageDER( + message: Uint8Array | string, + signature: Uint8Array | string, + ) { + if (typeof message === 'string') { + message = utf8ToBin(message); + } + + const messageHash = sha256.hash(message); + + return this.verifyMessageHashDER(messageHash, signature); + } + /** * Verify that a compact message hash was signed with the corresponding Private Key. * - * @param messageHash {Uint8Array} The hash of the message. - * @param signature {Uint8Array) The signature of the message. + * @param messageHash The hash of the message. + * @param signature The signature of the message. * - * @returns {boolean} True if signature and message hash verifies, false otherwise.. + * @returns True if signature and message hash verifies, false otherwise.. */ public verifyMessageHashCompact( messageHash: Uint8Array, - signature: Uint8Array + signature: Uint8Array, ): boolean { const validity = secp256k1.verifySignatureCompact( signature, this.bytes, - messageHash + messageHash, ); return validity; @@ -118,19 +158,27 @@ export class PublicKey { /** * Verify that a DER message hash was signed with the corresponding Private Key. * - * @param messageHash {Uint8Array} The hash of the message. - * @param signature {Uint8Array) The signature of the message. + * @param messageHash The hash of the message. + * @param signature The signature of the message. * - * @returns {boolean} True if signature and message hash verifies, false otherwise.. + * @returns True if signature and message hash verifies, false otherwise.. */ public verifyMessageHashDER( - messageHash: Uint8Array, - signature: Uint8Array + messageHash: Uint8Array | string, + signature: Uint8Array | string, ): boolean { + if (typeof messageHash === 'string') { + messageHash = hexToBin(messageHash); + } + + if (typeof signature === 'string') { + signature = hexToBin(signature); + } + const validity = secp256k1.verifySignatureDER( signature, this.bytes, - messageHash + messageHash, ); return validity; @@ -139,19 +187,27 @@ export class PublicKey { /** * Verify that a Schnorr message hash was signed with the corresponding Private Key. * - * @param messageHash {Uint8Array} The hash of the message. - * @param signature {Uint8Array) The signature of the message. + * @param messageHash The hash of the message. + * @param signature The signature of the message. * - * @returns {boolean} True if signature and message hash verifies, false otherwise.. + * @returns True if signature and message hash verifies, false otherwise.. */ public verifyMessageHashSchnorr( - messageHash: Uint8Array, - signature: Uint8Array + messageHash: Uint8Array | string, + signature: Uint8Array | string, ): boolean { + if (typeof messageHash === 'string') { + messageHash = hexToBin(messageHash); + } + + if (typeof signature === 'string') { + signature = hexToBin(signature); + } + const validity = secp256k1.verifySignatureSchnorr( signature, this.bytes, - messageHash + messageHash, ); return validity; @@ -160,7 +216,7 @@ export class PublicKey { /** * Encode the raw bytes of this Public Key to a hexadecimal representation. * - * @returns {string} The raw bytes Public Key encoded as Hexadecimal. + * @returns The raw bytes Public Key encoded as Hexadecimal. */ public toHex(): string { return binToHex(this.bytes); @@ -169,7 +225,7 @@ export class PublicKey { /** * Get the raw bytes of this Public Key. * - * @returns {Uint8Array} The raw bytes of the Public Key. + * @returns The raw bytes of the Public Key. */ public toBytes(): Uint8Array { // Return the raw bytes for this Public Key. @@ -179,7 +235,7 @@ export class PublicKey { /** * Return the hexadecimal representation of the raw bytes of the Public Key. * - * @returns {string} The raw bytes of the Public Key as hexadecimal. + * @returns The raw bytes of the Public Key as hexadecimal. */ public toString(): string { // Return the raw Public Key byes encoded as a hexadecimal string. diff --git a/packages/primitives/src/script.ts b/packages/primitives/src/script.ts index f68301c..0d6e725 100644 --- a/packages/primitives/src/script.ts +++ b/packages/primitives/src/script.ts @@ -6,6 +6,7 @@ import { binToHex, hexToBin, isHex, + parseScript, } from '@bitauth/libauth'; /** @@ -23,6 +24,19 @@ export class Script { this.bytes = bytes; } + /** + * Create a Script Entity from the given Assembly String. + * + * @param asm {string} String of assembly code. + * + * @throws {Error} If script cannot be decoded. + * + * @returns {Script} The created Script Entity. + */ + public static fromBytes(bytes: Uint8Array): Script { + return new Script(bytes); + } + /** * Create a Script Entity from the given Assembly String. * diff --git a/packages/primitives/src/transaction-hash.ts b/packages/primitives/src/transaction-hash.ts index 01e8db5..483fc45 100644 --- a/packages/primitives/src/transaction-hash.ts +++ b/packages/primitives/src/transaction-hash.ts @@ -1,106 +1,95 @@ -import { - binToHex, - hexToBin, - hashTransaction, - isHex, -} from '@bitauth/libauth'; +import { binToHex, hexToBin, hashTransaction, isHex } from '@bitauth/libauth'; /** * Transaction Hash Primitive. */ -export class TransactionHash -{ - public readonly bytes: Uint8Array; +export class TransactionHash { + public readonly bytes: Uint8Array; - /** - * Instantiates TransactionHash from a Transaction Hash's bytes. - * - * @param bytes {Uint8Array} The Transaction Hash's bytes. - */ - constructor(bytes: Uint8Array) - { - // If bytes is not exactly 32, then this is an invalid transaction hash... - if(bytes.length !== 32) - { - throw(new Error(`Invalid Transaction Hash: Expected 32 bytes but received ${bytes.length}`)); - } + /** + * Instantiates TransactionHash from a Transaction Hash's bytes. + * + * @param bytes {Uint8Array} The Transaction Hash's bytes. + */ + constructor(bytes: Uint8Array) { + // If bytes is not exactly 32, then this is an invalid transaction hash... + if (bytes.length !== 32) { + throw new Error( + `Invalid Transaction Hash: Expected 32 bytes but received ${bytes.length}`, + ); + } - this.bytes = bytes; - } + this.bytes = bytes; + } - /** - * Instantiates TransactionHash from a hex string. - * - * @param hex {string} The hex string. - * - * @throws {Error} If hex is invalid. - * - * @returns {TransactionHash} Instance of TransactionHash. - */ - public static fromHex(hex: string): TransactionHash - { - if(!isHex(hex)) - { - throw(new Error('Invalid hex provided')); - } + /** + * Instantiates TransactionHash from a hex string. + * + * @param hex {string} The hex string. + * + * @throws {Error} If hex is invalid. + * + * @returns {TransactionHash} Instance of TransactionHash. + */ + public static fromHex(hex: string): TransactionHash { + if (!isHex(hex)) { + throw new Error('Invalid hex provided'); + } - const bytes = hexToBin(hex); + const bytes = hexToBin(hex); - return new TransactionHash(bytes); - } + return new TransactionHash(bytes); + } - /** - * Instantiates TransactionHash from a Transaction's bytes. - * - * @param transactionBytes {Uint8Array} The Transaction's bytes. - * - * @returns {TransactionHash} Instance of TransactionHash. - */ - public static fromTransactionBytes(transactionBytes: Uint8Array): TransactionHash - { - const transactionHash = hashTransaction(transactionBytes); + /** + * Instantiates TransactionHash from a Transaction's bytes. + * + * @param transactionBytes {Uint8Array} The Transaction's bytes. + * + * @returns {TransactionHash} Instance of TransactionHash. + */ + public static fromTransactionBytes( + transactionBytes: Uint8Array, + ): TransactionHash { + const transactionHash = hashTransaction(transactionBytes); - return new TransactionHash(hexToBin(transactionHash)); - } + return new TransactionHash(hexToBin(transactionHash)); + } - /** - * Instantiates TransactionHash from a Transaction's bytes in hex. - * - * @param transactionHex {Uint8Array} The Transaction's bytes in hex. - * - * @throws {Error} If hex is invalid. - * - * @returns {TransactionHash} Instance of TransactionHash. - */ - public static fromTransactionHex(transactionHex: string): TransactionHash - { - if(!isHex(transactionHex)) - { - throw(new Error('Invalid hex provided')); - } + /** + * Instantiates TransactionHash from a Transaction's bytes in hex. + * + * @param transactionHex {Uint8Array} The Transaction's bytes in hex. + * + * @throws {Error} If hex is invalid. + * + * @returns {TransactionHash} Instance of TransactionHash. + */ + public static fromTransactionHex(transactionHex: string): TransactionHash { + if (!isHex(transactionHex)) { + throw new Error('Invalid hex provided'); + } - const bytes = hexToBin(transactionHex); + const bytes = hexToBin(transactionHex); - return TransactionHash.fromTransactionBytes(bytes); - } + return TransactionHash.fromTransactionBytes(bytes); + } - /** - * Returns transaction hash as a hex string. - * - * @returns {string} Hex string of the Transaction Hash. - */ - public toHex(): string - { - return binToHex(this.bytes); - } + /** + * Returns transaction hash as a hex string. + * + * @returns {string} Hex string of the Transaction Hash. + */ + public toHex(): string { + return binToHex(this.bytes); + } - /** - * Returns transaction hash as a Uint8Array. - * - * @returns {Uint8Array} Uint8Array of the Transaction Hash. - */ - public toBytes(): Uint8Array - { - return this.bytes; - } + /** + * Returns transaction hash as a Uint8Array. + * + * @returns {Uint8Array} Uint8Array of the Transaction Hash. + */ + public toBytes(): Uint8Array { + return this.bytes; + } } diff --git a/packages/primitives/src/transaction.ts b/packages/primitives/src/transaction.ts index 7734233..3b25c8e 100644 --- a/packages/primitives/src/transaction.ts +++ b/packages/primitives/src/transaction.ts @@ -10,19 +10,47 @@ import { import type { TransactionBCH } from '@bitauth/libauth'; /** - * Transaction Entity. + * Transaction Primitive. */ export class Transaction { constructor(public transaction: TransactionBCH) {} /** - * Create a Transaction Entity from the given byte data. + * Instantiates a transaction from either: * - * @param bytes {Uint8Array} The raw byte data of the transaction. + * 1. Transaction bytecode + * 2. Transaction bytecode (in hexadecimal format) + * + * @param transaction The transaction in one of the above formats. * * @throws {Error} If transaction data cannot be decoded. * - * @returns {Transaction} The created Transaction Entity. + * @returns The created Transaction Primitive. + */ + public static from(transaction: Uint8Array | string) { + // Handle string (hex) case + if (typeof transaction === 'string') { + return Transaction.fromHex(transaction); + } + + // Handle Uint8Array case + if (transaction instanceof Uint8Array) { + return Transaction.fromBytes(transaction); + } + + throw new TypeError( + 'Invalid transaction input type. Expected Uint8Array, or hex string', + ); + } + + /** + * Create a Transaction Primitive from the given byte data. + * + * @param bytes The raw byte data of the transaction. + * + * @throws {Error} If transaction data cannot be decoded. + * + * @returns {Transaction} The created Transaction Primitive. */ public static fromBytes(bytes: Uint8Array): Transaction { // Attempt to decode the raw transaction bytes. @@ -33,18 +61,18 @@ export class Transaction { throw new Error(decodeResult); } - // Return a new instance of the Transaction Entity created from the decode result. + // Return a new instance of the Transaction Primitive created from the decode result. return new Transaction(decodeResult); } /** - * Create a Transaction Entity from the given byte data encoded as hexadecimal string. + * Create a Transaction Primitive from the given byte data encoded as hexadecimal string. * - * @param hex {string} The raw byte data (encoded as hexadecimal string) of the transaction. + * @param hex The raw byte data (encoded as hexadecimal string) of the transaction. * * @throws {Error} If transaction cannot be decoded. * - * @returns {Transaction} The created Transaction Entity. + * @returns {Transaction} The created Transaction Primitive. */ public static fromHex(hex: string): Transaction { // Ensure that the given string is hex. @@ -59,12 +87,17 @@ export class Transaction { return Transaction.fromBytes(bytes); } - public static fromAny(bytesOrHex: Uint8Array | string) { - if (typeof bytesOrHex === 'string') { - return Transaction.fromHex(bytesOrHex); - } - - return Transaction.fromBytes(bytesOrHex); + /** + * Create a Transaction Primitive from LibAuth's TransactionBCH type. + * + * @remarks This method is UNSAFE and should only be used where input is trusted. + * + * @param transaction The transaction object (as per LibAuth). + * + * @returns The created Transaction Primitive. + */ + public static fromRaw(transaction: TransactionBCH) { + return new this(transaction); } public toBytes(): Uint8Array { @@ -91,6 +124,30 @@ export class Transaction { return this.toHash().toBytes(); } + public toRaw(): TransactionBCH { + return this.transaction; + } + + public isCoinbase(): boolean { + // A coinbase transaction MUST only have one input + if (this.getInputs().length !== 1) { + return false; + } + + const input = this.getInputs()[0]; + + // Check if the outpoint transaction hash is all zeros (32 bytes of 0x00) + const isOutpointZero = input.outpointTransactionHash.every( + (byte) => byte === 0x00, + ); + + // Check if the outpoint index is 0xFFFFFFFF (or -1 in signed 32-bit integer representation) + const isOutpointIndexMax = input.outpointIndex === 0xffffffff; + + // Both conditions must be true for a coinbase transaction + return isOutpointZero && isOutpointIndexMax; + } + public getVersion() { return this.transaction.version; } @@ -106,24 +163,4 @@ export class Transaction { public getLocktime() { return this.transaction.locktime; } - - public isCoinbase(): boolean { - // A coinbase transaction MUST only have one input - if (this.getInputs().length !== 1) { - return false; - } - - const input = this.getInputs()[0]; - - // Check if the outpoint transaction hash is all zeros (32 bytes of 0x00) - const isOutpointZero = input.outpointTransactionHash.every( - (byte) => byte === 0x00 - ); - - // Check if the outpoint index is 0xFFFFFFFF (or -1 in signed 32-bit integer representation) - const isOutpointIndexMax = input.outpointIndex === 0xffffffff; - - // Both conditions must be true for a coinbase transaction - return isOutpointZero && isOutpointIndexMax; - } } diff --git a/packages/primitives/src/utils.ts b/packages/primitives/src/utils.ts new file mode 100644 index 0000000..372481b --- /dev/null +++ b/packages/primitives/src/utils.ts @@ -0,0 +1,168 @@ +import { Transaction } from './transaction.js'; + +import { binToHex } from '@bitauth/libauth'; + +/** + * Sort transactions by the order in which they should be broadcast. + */ +export function sortTransactions( + transactions: Array, +): Array { + const transactionMap = transactions.reduce( + (map, transaction) => { + const txHash = transaction.toHash().toHex(); + map[txHash] = transaction; + return map; + }, + {} as Record, + ); + + const visited = new Set(); + const order: Transaction[] = []; + + const visit = (txid: string) => { + if (visited.has(txid)) return; + + const tx = transactionMap[txid]; + if (!tx) return; + + visited.add(txid); + + // First, visit all transactions that this transaction depends on + for (const input of tx.getInputs()) { + const outpointTransactionHashHex = binToHex( + input.outpointTransactionHash, + ); + visit(outpointTransactionHashHex); + } + + order.push(tx); + }; + + // Start the topological sort from each transaction + for (const tx of Object.values(transactionMap)) { + visit(tx.toHash().toHex()); + } + + return order; +} + +export class ExtMap extends Map { + /** + * Creates a new ExtMap from an array using a key selector + */ + static fromArray( + array: readonly T[], + keySelector: (item: T) => K, + ): ExtMap { + const collection = new ExtMap(); + array.forEach((item) => { + const key = keySelector(item); + collection.set(key, item); + }); + return collection; + } + + static fromObject(object: { [key: PropertyKey]: T }): ExtMap { + const collection = new ExtMap(); + for (let [key, value] of Object.entries(object)) { + collection.set(key, value); + } + return collection; + } + + /** + * Flattens an array of ExtMaps into a single ExtMap + * Last collection's values take precedence for duplicate keys + */ + static flatten(collections: ExtMap[]): ExtMap { + const flattened = new ExtMap(); + + collections.forEach((collection) => { + for (const [key, value] of collection.entries()) { + flattened.set(key, value); + } + }); + + return flattened; + } + + getOrFail(key: string): T { + const value = this.get(key); + + if (!value) { + throw new Error(`Failed to find key "${key}"`); + } + + return value; + } + + /** + * Returns a new ExtMap with filtered entries + */ + filter(predicate: (value: T, key: PropertyKey) => boolean): ExtMap { + const filtered = new ExtMap(); + for (const [key, value] of this.entries()) { + if (predicate(value, key)) { + filtered.set(key, value); + } + } + return filtered; + } + + /** + * Returns a new ExtMap with mapped values + */ + map(mapper: (value: T, key: PropertyKey) => U): ExtMap { + const mapped = new ExtMap(); + for (const [key, value] of this.entries()) { + mapped.set(key, mapper(value, key)); + } + return mapped; + } + + /** + * Returns a new ExtMap with entries sorted by the given comparator + * @param comparator - Function to determine the sort order + * @returns A new ExtMap with sorted entries + */ + sort(comparator: (a: T, b: T) => number): ExtMap { + const entries = Array.from(this.entries()); + const sorted = new ExtMap(); + + entries + .sort(([, valueA], [, valueB]) => comparator(valueA, valueB)) + .forEach(([key, value]) => { + sorted.set(key, value); + }); + + return sorted; + } + + /** + * Converts the collection to a plain object + */ + toObject(): Record { + return Object.fromEntries(this.entries()); + } + + /** + * Converts the collection to an array of values + */ + toArray(): T[] { + return Array.from(this.values()); + } + + /** + * Converts the collection to a Set + * @param callback - Optional function to transform keys before adding to Set + * @returns A Set containing either the keys or transformed values + */ + toSet(callback?: (key: PropertyKey, value: T) => U): Set { + const set = new Set(); + for (const [key, value] of this.entries()) { + set.add(callback ? callback(key, value) : (key as U)); + } + return set; + } +} diff --git a/packages/primitives/src/utxo.ts b/packages/primitives/src/utxo.ts index e69de29..4fb2ed1 100644 --- a/packages/primitives/src/utxo.ts +++ b/packages/primitives/src/utxo.ts @@ -0,0 +1,78 @@ +import { type Input, type Output, binToHex } from '@bitauth/libauth'; +import { ExtMap } from './utils.js'; + +// TODO: I want to make these a class. +// But it's really painful to do in TS. +// TS cannot extend an interface, it can only implement them. +// This means that we would have to redefine each property explicitly. +// But a getId function on UTXOs would be ergonomic. +// TODO: Maybe just compose it and have an "output" object, ya' dingus. + +export class Outpoint { + constructor( + public outpointTransactionHash: Uint8Array, + public outpointIndex: number, + ) {} + + static fromInput( + input: Pick, + ) { + return new this(input.outpointTransactionHash, input.outpointIndex); + } + + toString() { + const outpointHashHex = binToHex(this.outpointTransactionHash); + return `${outpointHashHex}:${this.outpointIndex}`; + } +} + +export class UTXO { + constructor( + public outpoint: Outpoint, + public output: Output, + ) {} + + static from(outpoint: Outpoint, output: Output) { + return new this(outpoint, output); + } + + flatten() { + return { + ...this.outpoint, + ...this.output, + }; + } +} + +export type UTXOSet = ExtMap; + +// TODO: Sort me better. If you work out a better name for your UTXO Set above, you might be able to put me in that class. +// Or maybe I do just work best as a stand-alone function? +// “Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.” — John Carmack + +export function calculateBalanceSats(unspents: Array) { + // Iterate over each unspent and return the total satoshi balance. + return unspents.reduce((totalSats, unspent) => { + return (totalSats = totalSats + unspent.output.valueSatoshis); + }, 0n); +} + +export type TokenBalances = { [categoryId: string]: bigint }; + +export function calculateBalanceTokens(unspents: Array) { + // Declare an object to store token category IDs to their balances. + const tokenBalances: TokenBalances = {}; + + // Iterate through each unspent and, if it has a token, add it to our balances. + unspents.forEach((unspent) => { + if (unspent.output.token) { + const tokenCategoryHex = binToHex(unspent.output.token.category); + const currentTokenTotal = tokenBalances[tokenCategoryHex] || 0n; + const newTotal = currentTokenTotal + unspent.output.token.amount; + tokenBalances[tokenCategoryHex] = newTotal; + } + }); + + // Return the token balances map. + return tokenBalances; +} diff --git a/packages/primitives/tests/mnemonic.spec.ts b/packages/primitives/tests/mnemonic.spec.ts new file mode 100644 index 0000000..5156a95 --- /dev/null +++ b/packages/primitives/tests/mnemonic.spec.ts @@ -0,0 +1,320 @@ +import { describe, it, expect } from 'vitest'; +import { Mnemonic, MnemonicRaw } from '~/src/mnemonic'; + +describe('Success cases', () => { + // Simple array of cases + const testCases: Array<{ + fromString: string; + fromWords: Array; + passphrase?: string; + raw: MnemonicRaw; + toString: string; + toStringWithoutPassphrase: string; + toWords: Array; + toWordsWithoutPassphrase: Array; + }> = [ + // No pass-phrase + { + fromString: + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust', + fromWords: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ], + raw: { + words: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ], + }, + toString: + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust', + toStringWithoutPassphrase: + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust', + toWords: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ], + toWordsWithoutPassphrase: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ], + }, + // Passphrase + { + fromString: + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust GeneralProtocols!', + fromWords: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + 'GeneralProtocols!', + ], + passphrase: 'GeneralProtocols!', + raw: { + words: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ], + passphrase: 'GeneralProtocols!', + }, + toString: + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust GeneralProtocols!', + toStringWithoutPassphrase: + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust', + toWords: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + 'GeneralProtocols!', + ], + toWordsWithoutPassphrase: [ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ], + }, + ]; + + it.each(testCases)( + 'from(string)', + ({ fromString: phrase, raw: expected }) => { + const mnemonic = Mnemonic.from(phrase); + expect(mnemonic.toRaw()).toStrictEqual(expected); + }, + ); + + it.each(testCases)('from(words)', ({ fromWords, raw: expected }) => { + const mnemonic = Mnemonic.from(fromWords); + expect(mnemonic.toRaw()).toStrictEqual(expected); + }); + + it.each(testCases)('fromString', ({ fromString, raw: expected }) => { + const mnemonic = Mnemonic.fromString(fromString); + expect(mnemonic.toRaw()).toStrictEqual(expected); + }); + + it.each(testCases)('fromWords', ({ fromWords, raw: expected }) => { + const mnemonic = Mnemonic.fromWords(fromWords); + expect(mnemonic.toRaw()).toStrictEqual(expected); + }); + + it.each(testCases)('fromWords', ({ fromWords, raw: expected }) => { + const mnemonic = Mnemonic.fromWords(fromWords); + expect(mnemonic.toRaw()).toStrictEqual(expected); + }); + + it.each(testCases)('toString', ({ toString, raw }) => { + const mnemonic = Mnemonic.fromRaw(raw); + expect(mnemonic.toString()).toStrictEqual(toString); + }); + + it.each(testCases)( + 'toString(withoutPassphrase)', + ({ toStringWithoutPassphrase, raw }) => { + const mnemonic = Mnemonic.fromRaw(raw); + expect(mnemonic.toString(false)).toStrictEqual(toStringWithoutPassphrase); + }, + ); + + it.each(testCases)('toWords', ({ toWords, raw }) => { + const mnemonic = Mnemonic.fromRaw(raw); + expect(mnemonic.toWords()).toStrictEqual(toWords); + }); + + it.each(testCases)( + 'toWords(withoutPassphrase)', + ({ toWordsWithoutPassphrase, passphrase, raw }) => { + const mnemonic = Mnemonic.fromRaw(raw); + expect(mnemonic.toWords(false)).toStrictEqual(toWordsWithoutPassphrase); + }, + ); + + it.each(testCases)('getPassphrase', ({ passphrase, raw }) => { + const mnemonic = Mnemonic.fromRaw(raw); + expect(mnemonic.getPassphrase()).toStrictEqual(passphrase); + }); +}); + +describe('Generate Cases', () => { + it('generateRandom should return instance of Mnemonic', () => { + const mnemonic = Mnemonic.generateRandom(); + expect(mnemonic).toBeInstanceOf(Mnemonic); + }); + + it('generateRandom should return instance of Mnemonic with passphrase', () => { + const mnemonic = Mnemonic.generateRandom('GeneralProtocols!'); + expect(mnemonic).toBeInstanceOf(Mnemonic); + expect(mnemonic.getPassphrase()).toBe('GeneralProtocols!'); + }); +}); + +describe('Validations', () => { + it('isValid should succeed if valid mnemonic', () => { + const isValid = Mnemonic.isValid( + 'goose just fan kidney barrel album wave crowd movie flame cousin exhaust', + ); + expect(isValid).toBe(true); + }); + + it('isValid should fail if invalid mnemonic', () => { + // 11 words. + const isValid = Mnemonic.isValid( + 'goose just fan kidney barrel album wave crowd movie flame cousin', + ); + expect(isValid).toBe(false); + }); + + it('isValidWord should succeed if word is in word list', () => { + const isValid = Mnemonic.isValidWord('goose'); + expect(isValid).toBe(true); + }); + + it('isValidWord should fail if word is not in word list', () => { + const isValid = Mnemonic.isValidWord('fnord'); + expect(isValid).toBe(false); + }); +}); + +describe('Fail cases', () => { + it('fromString should fail if invalid number of words', () => { + expect(() => { + // 11 words. + Mnemonic.fromString( + 'goose just fan kidney barrel album wave crowd movie flame cousin', + ); + }).toThrow(); + }); + + it('generateRandom should fail if invalid wordList contains invalid number of words', () => { + expect(() => { + // 11 words. + Mnemonic.generateRandom('GeneralProtocols!', ['one', 'two', 'three']); + }).toThrow(); + }); + + it('fromWords should fail if invalid number of words', () => { + expect(() => { + // 11 words. + Mnemonic.fromWords([ + 'goose', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + ]); + }).toThrow(); + }); + + it('fromString should fail if word not in dictionary', () => { + expect(() => { + // Invalid word "fnord". + Mnemonic.fromString( + 'fnord just fan kidney barrel album wave crowd movie flame cousin exhaust', + ); + }).toThrow(); + }); + + it('fromWord should fail if word not in dictionary', () => { + expect(() => { + // Invalid word "fnord". + Mnemonic.fromWords([ + 'fnord', + 'just', + 'fan', + 'kidney', + 'barrel', + 'album', + 'wave', + 'crowd', + 'movie', + 'flame', + 'cousin', + 'exhaust', + ]); + }).toThrow(); + }); +}); diff --git a/packages/primitives/tsconfig.json b/packages/primitives/tsconfig.json index 91b6b51..1d08f4d 100644 --- a/packages/primitives/tsconfig.json +++ b/packages/primitives/tsconfig.json @@ -2,7 +2,12 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + "paths": { + "~/*": [ + "./*" + ] + } }, "include": [ "src/**/*" diff --git a/packages/primitives/typedoc.json b/packages/primitives/typedoc.json new file mode 100644 index 0000000..efa29da --- /dev/null +++ b/packages/primitives/typedoc.json @@ -0,0 +1,18 @@ +{ + "entryPointStrategy": "expand", + "entryPoints": ["src"], + "exclude": "**/index*.ts", + "excludeInternal": true, + "excludeProtected": true, + "excludePrivate": true, + "categorizeByGroup": false, + "indexFormat": "table", + "parametersFormat": "table", + "interfacePropertiesFormat": "table", + "classPropertiesFormat": "table", + "propertyMembersFormat": "table", + "typeDeclarationFormat": "table", + "enumMembersFormat": "table", + "useCodeBlocks": true, + "sort": ["static-first"] +} diff --git a/packages/primitives/vite.config.ts b/packages/primitives/vite.config.ts new file mode 100644 index 0000000..92df688 --- /dev/null +++ b/packages/primitives/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + // vitest config + }, + resolve: { + alias: { + '~': path.resolve(__dirname, './'), + }, + }, +}); diff --git a/packages/vue/README.md b/packages/stack/README.md similarity index 100% rename from packages/vue/README.md rename to packages/stack/README.md diff --git a/packages/wallet/package.json b/packages/stack/package.json similarity index 53% rename from packages/wallet/package.json rename to packages/stack/package.json index 96a0bd3..5a3fbf1 100644 --- a/packages/wallet/package.json +++ b/packages/stack/package.json @@ -1,11 +1,9 @@ { - "name": "@xo/wallet", + "name": "@xocash/stack", "version": "0.0.1", - "description": "XO Stack Wallet", + "description": "XO Cash Stack", "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "dist/index.d.ts", + "types": "dist/index.types.d.ts", "exports": { ".": { "browser": { @@ -22,16 +20,19 @@ "dist" ], "scripts": { + "bench": "vitest bench", "build": "tsc --build --clean && tsc --build", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "test": "vitest", + "test:coverage": "vitest run --coverage", + "watch": "tsc --watch" }, "author": "", "license": "ISC", "dependencies": { - "@electrum-cash/network": "^3.3.0", - "@xo/primitives": "^0.0.1", - "idb-keyval": "^6.2.1", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7" + "@electrum-cash/network": "^4.1.1", + "@electrum-cash/protocol": "2.1.0-development.8075178089", + "@xocash/primitives": "*", + "idb": "^8.0.0" } } diff --git a/packages/stack/src/blockchain/base-blockchain.ts b/packages/stack/src/blockchain/base-blockchain.ts new file mode 100644 index 0000000..ac7fd8a --- /dev/null +++ b/packages/stack/src/blockchain/base-blockchain.ts @@ -0,0 +1,155 @@ +import { EventEmitter } from '../utils'; + +import { + type AddressCallback, + type BlockchainUTXOs, + type BlockchainTransactions, + type TransactionCallback, +} from './types.js'; + +import { type Transaction } from '@xocash/primitives'; + +export type BlockchainEvents = { + isConnectedUpdated: boolean; + blockHeightUpdated: number | undefined; +}; + +/** + * Abstract base class representing a blockchain interface implementation. + * Extends EventEmitter to provide blockchain-specific event handling capabilities. + * + * This class serves as a template for concrete blockchain implementations, + * providing a standardized interface for common blockchain operations such as + * fetching transactions, managing UTXOs, and handling blockchain subscriptions. + * + * @abstract + * @extends {EventEmitter} + */ +export abstract class BaseBlockchain extends EventEmitter { + // TODO: Lockscript might be better as the Subscribe Handler. + // From this, we can convert it to address. + // Fulcrum has Scripthash too - but I'm hesitant about this. + // Scripthash means SHA256'ing the Lockscript. + // This is fine - but, at scale, will become expensive. + // For example, imagine a HDWallet with 2000+ addresses. + // And we want to subscribe to all of them. + // Alternatively, we could just pass in an Address Primitive? + // And then have the Adapter decide what it wants to use? + + /** + * Initializes the blockchain connection and required resources. + * Should be called before performing any blockchain operations. + * + * @abstract + * @returns {Promise} A promise that resolves when the blockchain connection is established + * @throws {Error} If the connection cannot be established + */ + abstract start(): Promise; + + /** + * Gracefully terminates the blockchain connection and cleans up resources. + * Should be called when blockchain operations are no longer needed. + * + * @abstract + * @returns {Promise} A promise that resolves when the shutdown is complete + */ + abstract stop(): Promise; + + /** + * Retrieves all unspent transaction outputs (UTXOs) for a given address. + * + * @abstract + * @param {string} address - The blockchain address to query + * @returns {Promise} A promise that resolves with the unspent outputs + * @throws {Error} If the address is invalid or the query fails + */ + abstract fetchUnspents(address: string): Promise; + + /** + * Retrieves detailed information about a specific transaction. + * + * @abstract + * @param {string} txHash - The transaction hash/ID to look up + * @returns {Promise} A promise that resolves with the transaction details + * @throws {Error} If the transaction cannot be found or the query fails + */ + abstract fetchTransaction(txHash: string): Promise; + + /** + * Retrieves all transactions associated with a specific address. + * + * @abstract + * @param {string} address - The blockchain address to query + * @returns {Promise} A promise that resolves with the list of transactions + * @throws {Error} If the address is invalid or the query fails + */ + abstract fetchTransactions(address: string): Promise; + + /** + * Broadcasts a raw transaction to the blockchain network. + * + * @abstract + * @param {Uint8Array} transaction - The serialized transaction data to broadcast + * @returns {Promise} A promise that resolves when the transaction is successfully broadcast + * @throws {Error} If the transaction is invalid or broadcasting fails + */ + abstract broadcastTransaction(transaction: Uint8Array): Promise; + + /** + * Subscribes to updates for a specific blockchain address. + * The provided callback will be invoked when relevant events occur for the address. + * + * @abstract + * @param {string} address - The blockchain address to monitor + * @param {AddressCallback} callback - The callback function to handle address-related events + * @returns {Promise} A promise that resolves when the subscription is established + * @throws {Error} If the subscription cannot be created + */ + abstract subscribeAddress( + address: string, + callback: AddressCallback, + ): Promise; + + /** + * Removes a subscription for a specific blockchain address. + * + * @abstract + * @param {string} address - The blockchain address to unsubscribe from + * @param {AddressCallback} callback - The callback function to remove + * @returns {Promise} A promise that resolves when the subscription is removed + * @throws {Error} If the subscription cannot be removed + */ + abstract unsubscribeAddress( + address: string, + callback: AddressCallback, + ): Promise; + + /** + * Subscribes to updates for a specific transaction. + * The provided callback will be invoked when relevant events occur for the transaction. + * + * @abstract + * @param {string} transactionHash - The transaction hash to monitor + * @param {TransactionCallback} callback - The callback function to handle transaction-related events + * @returns {Promise} A promise that resolves when the subscription is established + * @throws {Error} If the subscription cannot be created + */ + abstract subscribeTransaction( + transactionHash: string, + callback: TransactionCallback, + ): Promise; + + /** + * Removes a subscription for a specific transaction. + * + * @abstract + * @param {string} transactionHash - The transaction hash to unsubscribe from + * @param {TransactionCallback} callback - The callback function to remove + * @returns {Promise} A promise that resolves when the subscription is removed + * @throws {Error} If the subscription cannot be removed + */ + abstract unsubscribeTransaction( + transactionHash: string, + callback: TransactionCallback, + ): Promise; +} diff --git a/packages/stack/src/blockchain/blockchain-electrum.ts b/packages/stack/src/blockchain/blockchain-electrum.ts new file mode 100644 index 0000000..4ccd83e --- /dev/null +++ b/packages/stack/src/blockchain/blockchain-electrum.ts @@ -0,0 +1,544 @@ +import { BaseBlockchain } from './base-blockchain.js'; +import { + type AddressCallback, + type TransactionCallback, + type BlockchainTransaction, + type BlockchainUTXO, +} from './types.js'; + +import { type BaseStore, StoreInMemory } from '../storage'; + +import { + Address, + ExtMap, + Outpoint, + Transaction, + TransactionHash, + UTXO, +} from '@xocash/primitives'; +import { ElectrumClient, RPCParameter } from '@electrum-cash/network'; + +import { + type ElectrumProtocolEvents, + type AddressSubscribeNotification, + type HeadersSubscribeNotification, + type TransactionSubscribeNotification, + initializeElectrumClient, + broadcastTransaction, + fetchUnspentTransactionOutputs, + fetchHistory, + fetchTransaction, + subscribeToBlockheaderUpdates, +} from '@electrum-cash/protocol'; + +import { hexToBin } from '@bitauth/libauth'; + +export interface BroadcastOpts { + timeoutMs: number; + waitUntilSeen: boolean; +} + +export type BlockchainElectrumEvents = { + isConnectedUpdated: boolean; + blockHeightUpdated: number | undefined; +}; + +export type BlockchainElectrumOpts = { + servers: Array; + application: string; +}; + +export class BlockchainElectrum extends BaseBlockchain { + // Composables. + electrumClient!: ElectrumClient; + + // State. + isConnected = false; + blockHeight: number | undefined = undefined; + + // Subscriptions State. + addressSubscriptions: Map> = new Map(); + transactionSubscriptions: Map> = new Map(); + + constructor( + public readonly store: BaseStore = new StoreInMemory(), + public readonly opts: BlockchainElectrumOpts = { + servers: [ + 'bch.imaginary.cash', + 'cashnode.bch.ninja', + 'electrum.imaginary.cash', + ], + application: 'XOApp', + }, + ) { + super(); + } + + async start() { + // TODO: We want to use a Cluster instead of a Client, but as of 2022-07-07 there are issues with Subscriptions on Cluster. + // See: https://gitlab.com/GeneralProtocols/anyhedge/whitelabel/frontend/-/issues/76 + + // Iterate over each server in our list of servers. + // NOTE: We are still using an Electrum Client due to the above bug. + // But, if we cannot connect to the first server, we will try the second (and third if given, etc) as a fallback. + for (const electrumServer of this.opts.servers) { + // Initialize an Electrum client using the given server. + const electrum = await initializeElectrumClient( + this.opts.application, + electrumServer, + ); + + // Attempt to connect to this Electrum Server. + try { + await electrum.connect(); + + this.isConnected = true; + + this.emit('isConnectedUpdated', this.isConnected); + } catch (error) { + console.warn(error); + + // If it fails to connect, try the next server in our list. + continue; + } + + // Save the client. + this.electrumClient = electrum; + + // Setup notification handler for connected event. + this.electrumClient.on('connected', () => { + // Set the isConnected property to true. + this.isConnected = true; + + // Emit an event. + this.emit('isConnectedUpdated', this.isConnected); + }); + + // Setup notification handler for disconnected event. + this.electrumClient.on('disconnected', () => { + // Set the isConnected property to false. + this.isConnected = false; + + // Emit an event. + this.emit('isConnectedUpdated', this.isConnected); + }); + + // TODO: Implement me and work out interface. + this.electrumClient.on( + 'blockchain.headers.subscribe', + this.handleNewBlockchainHeaders.bind(this), + ); + + this.electrumClient.on( + 'blockchain.address.subscribe', + this.handleAddressStatusUpdates.bind(this), + ); + this.electrumClient.on( + 'blockchain.transaction.subscribe', + this.handleTransactionStatusUpdates.bind(this), + ); + + // Subscribe to block header notifications. + await subscribeToBlockheaderUpdates(this.electrumClient); + + // Return to prevent further execution. + return; + } + + // Throw an error if we failed to connect to any of our Electrum Servers. + throw new Error( + 'Failed to connect to Electrum. Please try restarting your browser and ensure you are currently connected to the internet.', + ); + } + + async stop() { + // Disconnect from Electrum. + this.electrumClient.disconnect(); + } + + async fetchUnspents(address: string) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Define the cache key. + const cacheKey = `addressUnspents.${address}`; + + // Attempt to get the address unspents from the cache or fetch them if not available. + const addressUnspents = await this.store.getOrSet(cacheKey, async () => { + return await fetchUnspentTransactionOutputs( + this.electrumClient, + address, + true, + true, + ); + }); + + // Work out the locking byecode from the address. + const lockingBytecode = Address.from(address).toLockscriptBytes(); + + // Transform the unspents into our UTXO format. + const utxos: Array = addressUnspents.map((unspent) => { + const outpoint = new Outpoint(hexToBin(unspent.tx_hash), unspent.tx_pos); + + return { + height: unspent.height, + utxo: UTXO.from(outpoint, { + valueSatoshis: BigInt(unspent.value), + token: unspent.token_data + ? { + amount: BigInt(unspent.token_data.amount), + category: hexToBin(unspent.token_data.category), + nft: unspent.token_data.nft + ? { + capability: unspent.token_data.nft.capability, + commitment: hexToBin(unspent.token_data.nft.commitment), + } + : undefined, + } + : undefined, + lockingBytecode, + }), + }; + }); + + // Index the UTXOs by hashInHex:index. + const utxosIndexed = ExtMap.fromArray(utxos, (blockchainUTXO) => { + return blockchainUTXO.utxo.outpoint.toString(); + }); + + // Return the unspents. + return utxosIndexed; + } + + async fetchTransaction(txHash: string) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Define the cache key. + const cacheKey = `transactions.${txHash}`; + + // Attempt to get the transaction hex from the cache or fetch it if not available. + const transactionHex = await this.store.getOrSet(cacheKey, async () => { + return await fetchTransaction(this.electrumClient, txHash); + }); + + // Decode the hex into our transaction primitive. + const transaction = Transaction.fromHex(transactionHex); + + // Attempt to get the transaction hex from the cache or fetch it if not available. + // NOTE: We store JSON as opposed to hex because it is MUCH faster to decode. + /* + const transaction = new Transaction( + await this.store.getOrSet(cacheKey, async () => { + const tx = await fetchTransaction(this.electrumClient, txHash); + return Transaction.fromHex(tx).transaction; + }), + ); + */ + + // Return the transaction hex. + return transaction; + } + + async fetchTransactions(address: string) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Define the cache key. + const cacheKey = `addressHistory.${address}`; + + // Attempt to get the transaction history from the cache or fetch them if not available. + const addressHistory = await this.store.getOrSet(cacheKey, async () => { + return await fetchHistory(this.electrumClient, address); + }); + + // Get the transactions for each history item. + const transactions: Array = await Promise.all( + addressHistory.map(async (item) => ({ + hash: TransactionHash.fromHex(item.tx_hash), + height: item.height, + transaction: await this.fetchTransaction(item.tx_hash), + })), + ); + + // Index the transactions by their hash (in hex). + const transactionsIndexed = ExtMap.fromArray(transactions, (tx) => + tx.hash.toHex(), + ); + + // Return the transactions. + return transactionsIndexed; + } + + async waitForTransaction( + tx: Uint8Array | Transaction | string, + timeoutMs = 10_000, + ) { + // Cast the transaction. + if (!(tx instanceof Transaction)) { + tx = Transaction.from(tx); + } + + // Attempt to broadcast the transaction. + const waitForTxPromise = new Promise(async (resolve, reject) => { + // Set a timeout. + const timeout = setTimeout(() => { + reject(new Error(`Failed to broadcast ${tx.toHashHex()}`)); + }, timeoutMs); + + // Create a subscribe function so that we get notified when transaction is seen by network + const subscribeCallback = (_height: number | null) => { + clearTimeout(timeout); + this.unsubscribeTransaction(tx.toHashHex(), subscribeCallback); + resolve(); + }; + + // Subscribe to the transaction. + await this.subscribeTransaction(tx.toHashHex(), subscribeCallback); + }); + + return waitForTxPromise; + } + + // TODO: Split out the "waitUntilSeen" into own function. + // This'll be useful for protocols like BIP70/JPP/CashConnect, etc. + // Also consider a fail callback: + // If a "waitFor" fails, we probably want to invalidate the tx by sending those inputs elsewhere. + async broadcastTransaction( + tx: Transaction | Uint8Array | string, + opts: BroadcastOpts = { timeoutMs: 10_000, waitUntilSeen: true }, + ) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Cast the transaction. + if (!(tx instanceof Transaction)) { + tx = Transaction.from(tx); + } + + // Create a promise that will resolve when transaction is seen. + const waitForTxPromise = this.waitForTransaction(tx, opts.timeoutMs); + + // Broadcast the transaction to our Electrum node. + await broadcastTransaction(this.electrumClient, tx.toHex()); + + // Wait for the transaction to be seen. + await waitForTxPromise; + } + + async subscribeAddress(address: string, callback: AddressCallback) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Get or create the set of callbacks for this transaction + let callbacks = this.addressSubscriptions.get(address); + if (!callbacks) { + callbacks = new Set(); + this.addressSubscriptions.set(address, callbacks); + + await this.electrumClient.subscribe( + 'blockchain.address.subscribe', + address, + ); + } + + // Add the callback to the set + callbacks.add(callback); + } + + async unsubscribeAddress(address: string, callback: AddressCallback) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Get the callbacks set for this address + const callbacks = this.addressSubscriptions.get(address); + if (!callbacks) { + return; + } + + // Remove the specific callback + callbacks.delete(callback); + + // If there are no more callbacks, remove the address entry + // and potentially unsubscribe from blockchain updates + if (callbacks.size === 0) { + this.transactionSubscriptions.delete(address); + + await this.electrumClient.unsubscribe( + 'blockchain.address.unsubscribe', + address, + ); + } + } + + async subscribeTransaction( + transactionHash: string, + callback: TransactionCallback, + ) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Get or create the set of callbacks for this transaction + let callbacks = this.transactionSubscriptions.get(transactionHash); + if (!callbacks) { + callbacks = new Set(); + this.transactionSubscriptions.set(transactionHash, callbacks); + + await this.electrumClient.subscribe( + 'blockchain.transaction.subscribe', + transactionHash, + ); + } + + // Add the callback to the set + callbacks.add(callback); + } + + async unsubscribeTransaction( + transactionHash: string, + callback: TransactionCallback, + ) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + // Get the callbacks set for this transaction + const callbacks = this.transactionSubscriptions.get(transactionHash); + if (!callbacks) { + return; + } + + // Remove the specific callback + callbacks.delete(callback); + + // If there are no more callbacks, remove the transaction entry + // and potentially unsubscribe from blockchain updates + if (callbacks.size === 0) { + this.transactionSubscriptions.delete(transactionHash); + + await this.electrumClient.subscribe( + 'blockchain.transaction.subscribe', + transactionHash, + ); + } + } + + async request(method: string, ...parameters: Array) { + // Wait for connection to be established before attempting to subscribe. + // TODO: Consider doing this at a higher level. + if (!this.isConnected) { + await this.waitFor('isConnectedUpdated', (isConnected) => isConnected); + } + + const response = await this.electrumClient.request(method, ...parameters); + + if (response instanceof Error) { + throw response; + } + + return response; + } + + protected handleNewBlockchainHeaders( + notification: HeadersSubscribeNotification, + ) { + // Extract height from the first param. + // NOTE: This is an object. + // https://electrum-cash-protocol.readthedocs.io/en/latest/protocol-methods.html#blockchain-headers-subscribe + const { height } = notification.params[0]; + + // Assign the block height. + this.blockHeight = height; + + // Emit an event to update the blockheight. + this.emit('blockHeightUpdated', this.blockHeight); + } + + protected async handleAddressStatusUpdates( + notification: AddressSubscribeNotification, + ) { + // Extract parameters. + const address = notification.params[0]; + const status = notification.params[1]; + + // Get the cached status hash for this address. + const cachedStatus = await this.store.get(`addressStatus.${address}`); + + // If our cache is outdated, clear the caches for this address. + if (cachedStatus !== status) { + await this.store.delete(`addressUnspents.${address}`); + await this.store.delete(`addressHistory.${address}`); + } + + // Cache the new status. + await this.store.set(`addressStatus.${address}`, status); + + // Get the callbacks for this transaction + const callbacks = this.addressSubscriptions.get(address); + if (!callbacks) { + return; + } + + // Execute all callbacks for this transaction. + for (const callback of callbacks) { + try { + callback(status); + } catch (error) { + // Handle or log any callback execution errors + console.error( + `Error executing callback for address ${address}:`, + error, + ); + } + } + } + + protected handleTransactionStatusUpdates( + notification: TransactionSubscribeNotification, + ) { + const txHash = notification.params[0]; + const height = notification.params[1]; + + // Get the callbacks for this transaction + const callbacks = this.transactionSubscriptions.get(txHash); + if (!callbacks) { + return; + } + + // Execute all callbacks for this transaction. + for (const callback of callbacks) { + try { + callback(height); + } catch (error) { + // Handle or log any callback execution errors + console.error( + `Error executing callback for transaction ${txHash}:`, + error, + ); + } + } + } +} diff --git a/packages/wallet/src/blockchain/blockchain-local.ts b/packages/stack/src/blockchain/blockchain-local.ts similarity index 58% rename from packages/wallet/src/blockchain/blockchain-local.ts rename to packages/stack/src/blockchain/blockchain-local.ts index 2467376..1baf3af 100644 --- a/packages/wallet/src/blockchain/blockchain-local.ts +++ b/packages/stack/src/blockchain/blockchain-local.ts @@ -1,11 +1,20 @@ -import type { - AddressCallback, - IBlockchain, - TransactionCallback, - UTXO, -} from './interface.js'; +import { BaseBlockchain } from './base-blockchain.js'; +import { + type AddressCallback, + type TransactionCallback, + type BlockchainTransaction, + type BlockchainTransactions, + type BlockchainUTXO, + type BlockchainUTXOs, +} from './types.js'; -import { Address, Transaction } from '@xo/primitives'; +import { + Address, + ExtMap, + Outpoint, + Transaction, + UTXO, +} from '@xocash/primitives'; import { type Output, @@ -13,17 +22,17 @@ import { createVirtualMachineBCH, } from '@bitauth/libauth'; -export type UTXOSet = { [outpointHashAndIndex: string]: UTXO }; - // Local Blockchain Adapter. // This simulates the BCH Blockchain. -export class BlockchainLocal implements IBlockchain { - private transactions: Map; +export class BlockchainLocal extends BaseBlockchain { + private transactions: BlockchainTransactions; private addressSubscriptions: Map>; private transactionSubscriptions: Map>; constructor() { - this.transactions = new Map(); + super(); + + this.transactions = new ExtMap(); this.addressSubscriptions = new Map(); this.transactionSubscriptions = new Map(); } @@ -36,32 +45,43 @@ export class BlockchainLocal implements IBlockchain { console.log('BlockchainLocal: Stopped'); } - async fetchUnspents(address: string): Promise { + async fetchUnspents(address: string) { // Derive the current UTXO set. - const utxoSet = this.deriveUtxoSet(); - - // Filter for only UTXOs matching the given address. - return Object.values(utxoSet).filter((utxo) => { + const utxoSet = this.deriveUtxoSet().filter((blockchainUTXO) => { // Decode the address and locking bytecode. - const utxoAddress = Address.fromLockscriptBytes(utxo.lockingBytecode); - const needleAddress = Address.fromCashAddrOrLegacy(address); + const utxoAddress = Address.fromLockscriptBytes( + blockchainUTXO.utxo.output.lockingBytecode, + ); + const needleAddress = Address.from(address); // Compare the lockscript hex. return utxoAddress.toLockscriptHex() === needleAddress.toLockscriptHex(); }); + + return utxoSet; } - async fetchTransactions(address: string): Promise { - return this.getAllTransactions().filter((tx) => { + async fetchTransaction(txHash: string) { + const tx = this.transactions.get(txHash); + + if (!tx) { + throw new Error(`Transaction "${txHash}" does not exist`); + } + + return tx.transaction; + } + + async fetchTransactions(address: string) { + return this.transactions.filter((tx) => { // Check if any input or output involves the address // This is a simplified check and might need to be more sophisticated in a real implementation return ( - tx + tx.transaction .getInputs() .some((input) => - binToHex(input.unlockingBytecode).includes(address) + binToHex(input.unlockingBytecode).includes(address), ) || - tx + tx.transaction .getOutputs() .some((output) => binToHex(output.lockingBytecode).includes(address)) ); @@ -70,11 +90,11 @@ export class BlockchainLocal implements IBlockchain { async broadcastTransaction( tx: Transaction | Uint8Array | string, - opts: { skipValidation: boolean } = { skipValidation: false } + opts: { skipValidation: boolean } = { skipValidation: false }, ) { // Cast to transaction object. if (!(tx instanceof Transaction)) { - tx = Transaction.fromAny(tx); + tx = Transaction.from(tx); } // Derive the UTXO set so that we can find source outputs. @@ -87,15 +107,28 @@ export class BlockchainLocal implements IBlockchain { input.outpointIndex }`; - return utxoSet[utxoId]; + const blockchainUTXO = utxoSet.get(utxoId); + + if (!blockchainUTXO) { + throw new Error(`BlockchainUTXO "${utxoId}" does not exist`); + } + + return blockchainUTXO.utxo; }); // Validate the transaction. - BlockchainLocal.validateTransaction(tx, sourceOutputs); + BlockchainLocal.validateTransaction( + tx, + sourceOutputs.map((utxo) => utxo.output), + ); } // Add the transaction. - this.addTransaction(tx); + this.addTransaction({ + hash: tx.toHash(), + transaction: tx, + height: 0, + }); // Notify our address and transaction subscribers. this.notifyAddressSubscribers(tx); @@ -104,8 +137,7 @@ export class BlockchainLocal implements IBlockchain { async subscribeAddress(address: string, callback: AddressCallback) { // Convert the address to it's lockscript hex so that it's consistently represented. - const lockscriptHex = - Address.fromCashAddrOrLegacy(address).toLockscriptHex(); + const lockscriptHex = Address.from(address).toLockscriptHex(); // If no subscriptions exist yet on this lockscript, create a new Set. if (!this.addressSubscriptions.has(lockscriptHex)) { @@ -118,8 +150,7 @@ export class BlockchainLocal implements IBlockchain { async unsubscribeAddress(address: string, callback: AddressCallback) { // Convert the address to it's lockscript hex so that it's consistently represented. - const lockscriptHex = - Address.fromCashAddrOrLegacy(address).toLockscriptHex(); + const lockscriptHex = Address.from(address).toLockscriptHex(); // Find all callbacks this lockscript is subscribed to. const callbacks = this.addressSubscriptions.get(lockscriptHex); @@ -133,7 +164,7 @@ export class BlockchainLocal implements IBlockchain { async subscribeTransaction( transactionHash: string, - callback: TransactionCallback + callback: TransactionCallback, ) { if (!this.transactionSubscriptions.has(transactionHash)) { this.transactionSubscriptions.set(transactionHash, new Set()); @@ -143,7 +174,7 @@ export class BlockchainLocal implements IBlockchain { async unsubscribeTransaction( transactionHash: string, - callback: TransactionCallback + callback: TransactionCallback, ) { const callbacks = this.transactionSubscriptions.get(transactionHash); if (callbacks) { @@ -160,8 +191,9 @@ export class BlockchainLocal implements IBlockchain { // First, notify all source output listeners. tx.getInputs().forEach((input) => { - const sourceOutput = - utxoState[`${input.outpointTransactionHash};${input.outpointIndex}`]; + const sourceOutput = utxoState.get( + `${input.outpointTransactionHash};${input.outpointIndex}`, + ); // If there is no corresponding source output, skip this notification. // NOTE: This can occur if skipValidation is set to true in broadcast (which would be used for "coinbase" transactions). @@ -170,7 +202,7 @@ export class BlockchainLocal implements IBlockchain { } const address = Address.fromLockscriptBytes( - sourceOutput.lockingBytecode + sourceOutput.utxo.output.lockingBytecode, ).toLockscriptHex(); const callbacks = this.addressSubscriptions.get(address); @@ -183,7 +215,7 @@ export class BlockchainLocal implements IBlockchain { // Then notify all output listeners. tx.getOutputs().forEach((output) => { const address = Address.fromLockscriptBytes( - output.lockingBytecode + output.lockingBytecode, ).toLockscriptHex(); const callbacks = this.addressSubscriptions.get(address); @@ -203,127 +235,47 @@ export class BlockchainLocal implements IBlockchain { } } - addTransaction(tx: Transaction) { - this.transactions.set(tx.toHashHex(), tx); + addTransaction(tx: BlockchainTransaction) { + this.transactions.set(tx.hash.toHex(), tx); } removeTransaction(txHash: string) { this.transactions.delete(txHash); } - removeDependentTransactions(txHash: string): string[] { - const removedTxids: string[] = []; - const toRemove: Set = new Set([txHash]); - - const findDependents = (id: string) => { - for (const [otherTxid, tx] of this.transactions) { - if ( - tx - .getInputs() - .some((input) => binToHex(input.outpointTransactionHash) === id) - ) { - toRemove.add(otherTxid); - findDependents(otherTxid); - } - } - }; - - findDependents(txHash); - - for (const id of toRemove) { - if (this.transactions.has(id)) { - this.transactions.delete(id); - removedTxids.push(id); - } - } - - return removedTxids; - } - - getTransaction(txHash: string): Transaction | undefined { - return this.transactions.get(txHash); - } - - getAllTransactions(): Transaction[] { - return Array.from(this.transactions.values()); - } - - deriveUtxoSet(): UTXOSet { - const utxoMap: UTXOSet = {}; + deriveUtxoSet(): BlockchainUTXOs { + const utxoMap: BlockchainUTXOs = new ExtMap(); // First, add all outputs as potential UTXOs for (const tx of this.transactions.values()) { - const txHash = tx.toHash(); - - tx.getOutputs().forEach((output, outpointIndex) => { - const utxo: UTXO = { - outpointTransactionHash: txHash.toBytes(), - outpointIndex, - valueSatoshis: output.valueSatoshis, - lockingBytecode: output.lockingBytecode, + tx.transaction.getOutputs().forEach((output, outpointIndex) => { + const utxo: BlockchainUTXO = { + height: 0, + utxo: UTXO.from(new Outpoint(tx.hash.toBytes(), outpointIndex), { + valueSatoshis: output.valueSatoshis, + lockingBytecode: output.lockingBytecode, + }), }; - utxoMap[`${txHash.toHex()}:${outpointIndex}`] = utxo; + utxoMap.set(`${tx.hash.toHex()}:${outpointIndex}`, utxo); }); } // Then, remove all spent outputs for (const tx of this.transactions.values()) { - tx.getInputs().forEach((input) => { + tx.transaction.getInputs().forEach((input) => { const outpointTransactionHashHex = binToHex( - input.outpointTransactionHash + input.outpointTransactionHash, ); - delete utxoMap[`${outpointTransactionHashHex}:${input.outpointIndex}`]; + utxoMap.delete(`${outpointTransactionHashHex}:${input.outpointIndex}`); }); } return utxoMap; } - getDependentTransactions(txHash: string): Transaction[] { - return this.getAllTransactions().filter((tx) => { - tx.getInputs().some((input) => { - const outpointTransactionHashHex = binToHex( - input.outpointTransactionHash - ); - return outpointTransactionHashHex === txHash; - }); - }); - } - - getBroadcastOrder(): Transaction[] { - const visited = new Set(); - const order: Transaction[] = []; - - const visit = (txid: string) => { - if (visited.has(txid)) return; - - const tx = this.transactions.get(txid); - if (!tx) return; - - visited.add(txid); - - // First, visit all transactions that this transaction depends on - for (const input of tx.getInputs()) { - const outpointTransactionHashHex = binToHex( - input.outpointTransactionHash - ); - visit(outpointTransactionHashHex); - } - - order.push(tx); - }; - - // Start the topological sort from each transaction - for (const tx of this.transactions.values()) { - visit(tx.toHashHex()); - } - - return order; - } - /** - * @todo Consider moving this to Transaction Primitive. Trade-off is that it's probably big. Maybe standalone util? + * @remarks Consider moving this to Transaction Primitive. Trade-off is that it's probably big. Maybe standalone util? */ static validateTransaction(tx: Transaction, sourceOutputs: Array) { const virtualMachine = createVirtualMachineBCH(); diff --git a/packages/wallet/src/blockchain/blockchain.ts b/packages/stack/src/blockchain/blockchain.ts similarity index 71% rename from packages/wallet/src/blockchain/blockchain.ts rename to packages/stack/src/blockchain/blockchain.ts index a372f39..f3ab834 100644 --- a/packages/wallet/src/blockchain/blockchain.ts +++ b/packages/stack/src/blockchain/blockchain.ts @@ -1,8 +1,9 @@ -import { IBlockchain, AddressCallback, TransactionCallback } from './interface'; +import { BaseBlockchain } from './base-blockchain.js'; +import { type AddressCallback, type TransactionCallback } from './types.js'; import { BlockchainElectrum } from './blockchain-electrum.js'; import { BlockchainLocal } from './blockchain-local.js'; -import { Transaction } from '@xo/primitives'; +import { Transaction } from '@xocash/primitives'; /** * The idea here is that this will implement the IBlockchain interface. @@ -18,17 +19,15 @@ import { Transaction } from '@xo/primitives'; * ]) * ``` */ -export class Blockchain implements IBlockchain { +export class Blockchain extends BaseBlockchain { blockchainLocal: BlockchainLocal; blockchainElectrum: BlockchainElectrum; constructor() { + super(); + this.blockchainLocal = new BlockchainLocal(); - this.blockchainElectrum = new BlockchainElectrum( - ['bch.imaginary.cash', 'cashnode.bch.ninja', 'electrum.imaginary.cash'], - 'BCHApp', - '1.5' - ); + this.blockchainElectrum = new BlockchainElectrum(); } async start() { @@ -53,6 +52,14 @@ export class Blockchain implements IBlockchain { } } + async fetchTransaction(txHash: string) { + if (this.blockchainElectrum.isConnected) { + return await this.blockchainElectrum.fetchTransaction(txHash); + } else { + return await this.blockchainLocal.fetchTransaction(txHash); + } + } + async fetchTransactions(address: string) { if (this.blockchainElectrum.isConnected) { return await this.blockchainElectrum.fetchTransactions(address); @@ -69,24 +76,24 @@ export class Blockchain implements IBlockchain { } } - async subscribeAddress(address: string, callback: AddressCallback) { + async subscribeAddress(_address: string, _callback: AddressCallback) { throw new Error('Not yet implemented'); } - async unsubscribeAddress(address: string, callback: AddressCallback) { + async unsubscribeAddress(_address: string, _callback: AddressCallback) { throw new Error('Not yet implemented'); } async subscribeTransaction( - transactionHash: string, - callback: TransactionCallback + _transactionHash: string, + _callback: TransactionCallback, ) { throw new Error('Not yet implemented'); } async unsubscribeTransaction( - transactionHash: string, - callback: TransactionCallback + _transactionHash: string, + _callback: TransactionCallback, ) { throw new Error('Not yet implemented'); } diff --git a/packages/wallet/src/blockchain/index.ts b/packages/stack/src/blockchain/index.ts similarity index 58% rename from packages/wallet/src/blockchain/index.ts rename to packages/stack/src/blockchain/index.ts index e7fbef0..425fa3f 100644 --- a/packages/wallet/src/blockchain/index.ts +++ b/packages/stack/src/blockchain/index.ts @@ -1,5 +1,5 @@ +export * from './base-blockchain.js'; export * from './blockchain.js'; export * from './blockchain-electrum.js'; -export * from './blockchain-electrum-types.js'; export * from './blockchain-local.js'; -export * from './interface.js'; +export * from './types.js'; diff --git a/packages/stack/src/blockchain/types.ts b/packages/stack/src/blockchain/types.ts new file mode 100644 index 0000000..a0a6204 --- /dev/null +++ b/packages/stack/src/blockchain/types.ts @@ -0,0 +1,28 @@ +import { Transaction, TransactionHash, UTXO, ExtMap } from '@xocash/primitives'; + +export type AddressCallback = (status: string | null) => void; +export type TransactionCallback = (height: number | null) => void; + +export type BlockchainTransaction = { + hash: TransactionHash; + height: number; + transaction: Transaction; + // TODO: DSP + // Wait, why the fuck would I need a DSP for Transaction? Did I mean to do this on UTXO? +}; + +export type BlockchainTransactions = ExtMap; + +// TODO: You were considering using classes that extend INdexedCollection. E.g. +// class BlockchainUTXOs extends ExtMap +// This probably isn't a bad idea: +// 1. You nest calculateBalanceSats and calculateBalanceTokens under it +// 2. You could also do sorting methods (more pertinent for BLockchainTransactions above) +export type BlockchainUTXO = { + height: number; + utxo: UTXO; + // TODO: DSP +}; + +// NOTE: To minimize refactors later, use this name elsewhere; not the `ExtMap` form. +export type BlockchainUTXOs = ExtMap; diff --git a/packages/stack/src/index.browser.ts b/packages/stack/src/index.browser.ts new file mode 100644 index 0000000..c5fe44f --- /dev/null +++ b/packages/stack/src/index.browser.ts @@ -0,0 +1,5 @@ +// Export modules that work across all platforms. +export * from './index.js'; + +// These exports will only work in a browser environment. +export * from './storage/store-indexeddb.js'; diff --git a/packages/wallet/src/index.node.ts b/packages/stack/src/index.node.ts similarity index 70% rename from packages/wallet/src/index.node.ts rename to packages/stack/src/index.node.ts index 120101d..7ef43bf 100644 --- a/packages/wallet/src/index.node.ts +++ b/packages/stack/src/index.node.ts @@ -3,6 +3,4 @@ export * from './index.js'; // These exports will only work in a NodeJS (backend) environment. // NOTE: We import and export each module with a common alias so that any leveraging libraries can use the common name and maintain cross-platform compatibility. -import { StorageSQLite } from './storage/storage-sqlite.js'; -export { StorageSQLite }; -export { StorageSQLite as StorageLocal }; +// export * from './storage/storage-local.node.js'; diff --git a/packages/wallet/src/index.ts b/packages/stack/src/index.ts similarity index 52% rename from packages/wallet/src/index.ts rename to packages/stack/src/index.ts index be205ec..09bfb97 100644 --- a/packages/wallet/src/index.ts +++ b/packages/stack/src/index.ts @@ -1,3 +1,7 @@ +// Export all our primitives as a convenience. +export * from '@xocash/primitives'; + +// Export all of our stack-specific modules. export * from './blockchain/index.js'; export * from './storage/index.js'; export * from './utils/index.js'; diff --git a/packages/stack/src/index.types.ts b/packages/stack/src/index.types.ts new file mode 100644 index 0000000..1ebd9a6 --- /dev/null +++ b/packages/stack/src/index.types.ts @@ -0,0 +1,3 @@ +// Export all of our stack-specific modules. +export * from './index.browser.js'; +export * from './index.node.js'; diff --git a/packages/stack/src/p2p/TODO.md b/packages/stack/src/p2p/TODO.md new file mode 100644 index 0000000..96b2bac --- /dev/null +++ b/packages/stack/src/p2p/TODO.md @@ -0,0 +1 @@ +Will be nice if we can make this adapter based as with other services. diff --git a/packages/stack/src/storage/base-store.ts b/packages/stack/src/storage/base-store.ts new file mode 100644 index 0000000..c6419ca --- /dev/null +++ b/packages/stack/src/storage/base-store.ts @@ -0,0 +1,271 @@ +import { Encryption, EventEmitter } from '../utils'; + +export type StorageOpts = { + syncInMemory?: boolean; + encryption?: Encryption; +}; + +export type StoreEvents = { + set: { key: string; value: any }; + delete: { key: string }; + clear: undefined; +}; + +export abstract class BaseStorage { + abstract listStores(): Promise>; + abstract createStore( + storeName: string, + opts: StorageOpts, + ): Promise; + abstract getStore( + storeName: string, + opts: StorageOpts, + ): Promise; + abstract createOrGetStore( + storeName: string, + opts: StorageOpts, + ): Promise; + abstract deleteStore(storeName: string): Promise; + abstract deleteDatabase(): Promise; +} + +/** + * Represents a wrapper object for accessing and manipulating a stored value. + */ +export type StoreValueProxy = { + /** + * Gets the current value from the store + */ + get(): Promise; + + /** + * Gets the current value from the store or fails if undefined. + */ + getOrFail(): Promise; + + /** + * Sets a new value in the store + * @param newValue The value to store + */ + set(newValue: T): Promise; +}; + +/** + * Abstract base class for implementing key-value storage with event emission capabilities. + * Extends EventEmitter to provide event handling for store operations. + * + * @example + * ```typescript + * class MyStore extends BaseStore { + * async keys(): Promise> { + * // Implementation + * } + * + * async get(key: string): Promise { + * // Implementation + * } + * + * async set(key: string, value: T): Promise { + * // Implementation + * } + * + * async delete(key: string): Promise { + * // Implementation + * } + * } + * ``` + */ +export abstract class BaseStore extends EventEmitter { + /** + * Returns an array of all keys in the store. + */ + abstract keys(): Promise>; + + /** + * Retrieves a value from the store by its key. + * + * @template T The type of the value to retrieve + * @param key The key to lookup + */ + abstract get(key: string): Promise; + + /** + * Stores a value in the store with the specified key. + * + * @template T The type of the value to store + * @param key The key under which to store the value + * @param value The value to store + */ + abstract set(key: string, value: T): Promise; + + /** + * Removes a value from the store by its key. + * + * @param key The key to remove + */ + abstract delete(key: string): Promise; + + /** + * Retrieves all key-value pairs from the store. + * + * @template T The type of the values in the store + */ + async all(): Promise<{ [key: string]: T }> { + const keys = await this.keys(); + const values = await Promise.all(keys.map((key) => this.get(key))); + + return keys.reduce( + (acc, key, index) => { + const value = values[index]; + if (value !== undefined) { + acc[key] = value; + } + return acc; + }, + {} as { [key: string]: T }, + ); + } + + /** + * Removes all key-value pairs from the store. + */ + async clear(): Promise { + const keys = await this.keys(); + await Promise.all(keys.map((key) => this.delete(key))); + } + + /** + * Checks if a key exists in the store. + * + * @param key The key to check + */ + async has(key: string): Promise { + return (await this.get(key)) ? true : false; + } + + /** + * Retrieves multiple values from the store by their keys. + * Filters out undefined values from the results. + * + * @template T The type of the values to retrieve + * @param keys Array of keys to retrieve + */ + async batchGet(keys: Array): Promise> { + const results = await Promise.all( + keys.map(async (key) => { + const value = await this.get(key); + return [key, value] as const; + }), + ); + + return new Map( + results.filter(([_, value]) => value !== undefined) as [string, T][], + ); + } + + /** + * Stores multiple key-value pairs in the store. + * Emits 'set' events for each key-value pair. + * + * @template T The type of the values to store + * @param items Map of key-value pairs to store + */ + async batchSet(items: Map): Promise { + await Promise.all( + Array.from(items).map(([key, value]) => { + return this.set(key, value); + }), + ); + + items.forEach((value, key) => { + this.emit('set', { key, value }); + }); + } + + /** + * Closes the store connection. Override this method in implementing classes if needed. + */ + async close(): Promise {} + + /** + * Retrieves a value from the store by its key, throwing an error if the value doesn't exist. + * + * @template T The type of the value to retrieve + * @param key The key to lookup + */ + async getOrFail(key: string): Promise { + // Attempt to get the value from the store. + const value = await this.get(key); + + // If the value does not exist, throw an error. + if (typeof value === 'undefined') { + throw new Error(`Failed to get key "${key}"`); + } + + // Return the value. + return value; + } + + /** + * Retrieves a value from the store by its key, or sets it using the provided setter function if it doesn't exist. + * + * @template T The type of the value to retrieve or set + * @param key The key to lookup + * @param setter Function to generate the value if it doesn't exist + */ + async getOrSet(key: string, setter: () => T | Promise): Promise { + // Attempt to get the value from the store. + let value = await this.get(key); + + // If it is undefined, use the setter to set it. + if (typeof value === 'undefined') { + value = await setter(); + await this.set(key, value); + } + + // Return the value. + return value; + } + + /** + * Creates a wrapper object that provides convenient access to a stored value. + * The wrapper allows getting and setting the value using async property access. + * + * @template T The type of the value to wrap + * @param key The key under which the value is stored + * @returns An object with a getter and setter for the stored value + * + * @example + * ```typescript + * const numValue = store.proxy('myNumber'); + * const value = await numValue.get(); // gets the stored value + * await numValue.set(42); // sets the stored value + * ``` + */ + proxy(key: string): StoreValueProxy { + const self = this; + + return { + /** + * Gets the current value from the store + */ + get(): Promise { + return self.get(key); + }, + + /** + * Gets the current value from the store or fails if undefined + */ + getOrFail(): Promise { + return self.getOrFail(key); + }, + + /** + * Sets a new value in the store + */ + async set(newValue: T): Promise { + await self.set(key, newValue); + }, + }; + } +} diff --git a/packages/stack/src/storage/index.ts b/packages/stack/src/storage/index.ts new file mode 100644 index 0000000..c624b24 --- /dev/null +++ b/packages/stack/src/storage/index.ts @@ -0,0 +1,3 @@ +export * from './base-store.js'; +export * from './store-inmemory.js'; +export * from './store-inmemory-synced.js'; diff --git a/packages/stack/src/storage/store-indexeddb.ts b/packages/stack/src/storage/store-indexeddb.ts new file mode 100644 index 0000000..1672163 --- /dev/null +++ b/packages/stack/src/storage/store-indexeddb.ts @@ -0,0 +1,255 @@ +import { BaseStorage, BaseStore, StorageOpts } from './base-store.js'; +import { StoreInMemorySynced } from './store-inmemory-synced.js'; + +import { + Encryption, +} from '../utils'; + +import { + encodeExtendedJsonObject, + decodeExtendedJsonObject, +} from '@xocash/primitives'; + +import { openDB, deleteDB, IDBPDatabase } from 'idb'; + +export class StorageIndexedDB extends BaseStorage { + private constructor( + private db: IDBPDatabase, + private dbName: string, + ) { + super(); + } + + public static async createOrOpen(dbName: string) { + try { + // Check if database exists by attempting to open it at current version + const existingDb = await openDB(dbName, undefined); + return new StorageIndexedDB(existingDb, dbName); + } catch (error) { + // If database doesn't exist, create it at version 1 + const newDb = await openDB(dbName, 1, { + upgrade(_db) { + // Initial database creation - no stores needed by default + }, + }); + return new StorageIndexedDB(newDb, dbName); + } + } + + async listStores() { + return Array.from(this.db.objectStoreNames); + } + + async createStore(storeName: string, opts: StorageOpts = {}) { + if (!this.db.objectStoreNames.contains(storeName)) { + const currentVersion = this.db.version; + this.db.close(); + + const db = await openDB(this.dbName, currentVersion + 1, { + upgrade(db) { + db.createObjectStore(storeName); + }, + }); + this.db = db; + } + + const store = await StoreIndexedDB.create( + this.db, + storeName, + opts.encryption, + ); + + return opts.syncInMemory ? await StoreInMemorySynced.create(store) : store; + } + + async getStore(storeName: string, opts: StorageOpts = {}) { + try { + if (!this.db.objectStoreNames.contains(storeName)) { + return null; + } + + const store = await StoreIndexedDB.create( + this.db, + storeName, + opts.encryption, + ); + + return opts.syncInMemory ? StoreInMemorySynced.create(store) : store; + } catch (error) { + return null; + } + } + + async createOrGetStore(storeName: string, opts: StorageOpts = {}) { + const existing = await this.getStore(storeName, opts); + if (existing) { + return existing; + } + return this.createStore(storeName, opts); + } + + async deleteStore(storeName: string) { + if (this.db.objectStoreNames.contains(storeName)) { + const currentVersion = this.db.version; + this.db.close(); + + const db = await openDB(this.dbName, currentVersion + 1, { + upgrade(db) { + db.deleteObjectStore(storeName); + }, + }); + this.db = db; + } + } + + async deleteDatabase() { + this.db.close(); + await deleteDB(this.dbName); + } +} + +/** + * Implementation of BaseStore using IndexedDB as the storage backend. + */ +export class StoreIndexedDB extends BaseStore { + constructor( + public db: IDBPDatabase, + public storeName: string, + public encryption?: Encryption, + ) { + super(); + + /* + this.broadcastChannel = new BroadcastChannel( + `StoreIndexedDB:${db.name}.${this.storeName}`, + ); + + this.broadcastChannel.onmessage = (message) => { + this.emit('set', message); + }; + */ + } + + /** + * Factory method to create a new instance of StoreIndexedDB. + * Provides a more convenient way to instantiate the store when working with promises. + * + * @param db - An instance of an upgraded and opened IndexedDB database + * @param storeName - The name of the object store within the database to use + * + * @example + * ```typescript + * const store = await StoreIndexedDB.create(db, 'myStore'); + * ``` + */ + static async create( + db: IDBPDatabase, + storeName: string, + encryption?: Encryption, + ) { + return new this(db, storeName, encryption); + } + + async all() { + const tx = this.db.transaction(this.storeName, 'readonly'); + const store = tx.objectStore(this.storeName); + const keys = await store.getAllKeys(); + const values = await store.getAll(); + + return keys.reduce( + (acc, key, index) => { + acc[key.toString()] = decodeExtendedJsonObject(values[index]); + return acc; + }, + {} as { [key: string]: T }, + ); + } + + async keys() { + const tx = this.db.transaction(this.storeName, 'readonly'); + const store = tx.objectStore(this.storeName); + const keys = await store.getAllKeys(); + return keys.map((key) => key.toString()); + } + + async get(key: string) { + const encodedValue = await this.db.get(this.storeName, key); + + const decryptedValue = this.encryption + ? this.encryption.decrypt(encodedValue) + : encodedValue; + + return decodeExtendedJsonObject(decryptedValue) as T; + } + + async set(key: string, value: T) { + const encodedValue = encodeExtendedJsonObject(value); + + const encryptedValue = this.encryption + ? this.encryption.encrypt(encodedValue) + : encodedValue; + + await this.db.put(this.storeName, encryptedValue, key); + this.emit('set', { key, value }); + } + + async delete(key: string) { + await this.db.delete(this.storeName, key); + this.emit('delete', { key }); + } + + async batchGet(keys: string[]): Promise> { + const tx = this.db.transaction(this.storeName, 'readonly'); + const store = tx.objectStore(this.storeName); + + const results = await Promise.all( + keys.map(async (key) => { + const value = await store.get(key); + return [ + key, + value ? decodeExtendedJsonObject(value) : undefined, + ] as const; + }), + ); + + await tx.done; + + return new Map( + results.filter(([_, value]) => value !== undefined) as [string, T][], + ); + } + + async batchSet(items: Map) { + const tx = this.db.transaction(this.storeName, 'readwrite'); + const store = tx.objectStore(this.storeName); + + await Promise.all( + Array.from(items).map(([key, value]) => { + const encodedValue = encodeExtendedJsonObject(value); + return store.put(encodedValue, key); + }), + ); + + await tx.done; + + items.forEach((value, key) => { + this.emit('set', { key, value }); + }); + } + + async clear() { + await this.db.clear(this.storeName); + this.emit('clear', undefined); + } + + async has(key: string) { + const tx = this.db.transaction(this.storeName, 'readonly'); + const store = tx.objectStore(this.storeName); + const result = await store.getKey(key); + return result !== undefined; + } + + async close() { + this.db.close(); + } +} diff --git a/packages/stack/src/storage/store-inmemory-synced.ts b/packages/stack/src/storage/store-inmemory-synced.ts new file mode 100644 index 0000000..aa4b258 --- /dev/null +++ b/packages/stack/src/storage/store-inmemory-synced.ts @@ -0,0 +1,82 @@ +import { BaseStore } from './base-store.js'; +import { StoreInMemory } from './store-inmemory.js'; + +/** + * Storage Adapter that takes another Storage Adapter (e.g. IndexedDB) and "syncs" its contents to system memory for faster read access. + * + * All read operations will use system memory - all write operations will use the provided adapter. + */ +export class StoreInMemorySynced extends BaseStore { + constructor( + private inMemoryCache: StoreInMemory, + private store: BaseStore, + ) { + super(); + + // Hook into all write operations so that we can sync the In-Memory cache. + this.store.on('set', async (payload) => { + await this.inMemoryCache.set(payload.key, payload.value); + this.emit('set', { key: payload.key, value: payload.value }); + }); + + this.store.on('delete', async (payload) => { + await this.inMemoryCache.delete(payload.key); + this.emit('delete', { key: payload.key }); + }); + + this.store.on('clear', async () => { + await this.inMemoryCache.clear(); + this.emit('clear', undefined); + }); + } + + static async create(store: BaseStore) { + // Instantiate in-memory cache and IndexedDB. + const inMemoryCache = new StoreInMemory(); + + // Create instance of this store. + const memorySyncedStore = new this(inMemoryCache, store); + + // Sync the data from IndexedDB into the In-Memory cache. + const indexedDBEntries = await store.all(); + for (let [key, value] of Object.entries(indexedDBEntries)) { + inMemoryCache.set(key, value); + } + + // Return our instance of this store. + return memorySyncedStore; + } + + async all() { + return await this.inMemoryCache.all(); + } + + async keys() { + return await this.inMemoryCache.keys(); + } + + async get(key: string) { + // TODO: Consider whether you want to structuredClone this to prevent mutation. + return await this.inMemoryCache.get(key); + } + + async set(key: string, value: T) { + await this.store.set(key, value); + } + + async delete(key: string) { + await this.store.delete(key); + } + + async clear() { + await this.store.clear(); + } + + async has(key: string) { + return await this.inMemoryCache.has(key); + } + + async close() { + await this.store.close(); + } +} diff --git a/packages/stack/src/storage/store-inmemory.ts b/packages/stack/src/storage/store-inmemory.ts new file mode 100644 index 0000000..01dd8d7 --- /dev/null +++ b/packages/stack/src/storage/store-inmemory.ts @@ -0,0 +1,52 @@ +import { BaseStore } from './base-store.js'; + +/** + * Implementation of BaseStore using Memory as the storage backend. + * + * @remarks + * This implementation can be used testing and caching of expensive operations. + */ +export class StoreInMemory extends BaseStore { + private store: Map; + + constructor() { + super(); + + this.store = new Map(); + } + + async all() { + const result: { [key: string]: T } = {}; + for (const [key, value] of this.store.entries()) { + result[key] = value as T; + } + return result; + } + + async keys() { + return Array.from(this.store.keys()); + } + + async get(key: string) { + return this.store.get(key) as T; + } + + async set(key: string, value: T) { + this.store.set(key, value); + this.emit('set', { key, value }); + } + + async delete(key: string) { + this.store.delete(key); + this.emit('delete', { key }); + } + + async clear() { + this.store.clear(); + this.emit('clear', undefined); + } + + async has(key: string) { + return this.store.has(key); + } +} diff --git a/packages/stack/src/utils/encryption.ts b/packages/stack/src/utils/encryption.ts new file mode 100644 index 0000000..1fb0480 --- /dev/null +++ b/packages/stack/src/utils/encryption.ts @@ -0,0 +1,122 @@ +// TODO: Consider whether we should shuffle this to Stack Util (as opposed to Primitive). +// It is async which breaks from our Primitive patterns. + +import { utf8ToBin } from '@bitauth/libauth'; + +// NOTE: We need to provide a default salt otherwise we would be deriving a key per each operation. +// This would be far too computationally expensive. +// TODO: Allow the salt to be customized in an options argument to the constructor. +export const DEFAULT_XO_SALT = + 'cash.xo.default_salt_for_encryption_aabbccddeeff00112233445566778899'; + +export type EncryptionOpts = { + algorithm: string; + keyLength: number; + salt: Uint8Array; + ivLength: number; + iterations: number; +}; + +export class Encryption { + constructor( + private key: CryptoKey, + public opts: EncryptionOpts, + ) {} + + static async fromBytes(key: Uint8Array, opts: Partial = {}) { + // Merge the provided options into our default options. + const encryptionOpts = { + algorithm: 'AES-GCM', + keyLength: 256, + salt: utf8ToBin(DEFAULT_XO_SALT), + ivLength: 16, + iterations: 100_000, + ...opts, + }; + + // Create a key derivation key + const keyMaterialBase = await crypto.subtle.importKey( + 'raw', + key, + 'PBKDF2', + false, + ['deriveBits', 'deriveKey'], + ); + + // Derive the actual encryption key + const derivedKey = await crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt: encryptionOpts.salt, + iterations: encryptionOpts.iterations, + hash: 'SHA-256', + }, + keyMaterialBase, + { + name: encryptionOpts.algorithm, + length: encryptionOpts.keyLength, + }, + false, + ['encrypt', 'decrypt'], + ); + + return new Encryption(derivedKey, encryptionOpts); + } + + static async fromPassphrase( + passphrase: string, + opts: Partial = {}, + ) { + const key = utf8ToBin(passphrase); + + return this.fromBytes(key, opts); + } + + async encrypt(payload: Uint8Array | string): Promise { + // Convert string payload to Uint8Array if needed + const data = typeof payload === 'string' ? utf8ToBin(payload) : payload; + + // Generate random IV + const iv = crypto.getRandomValues(new Uint8Array(this.opts.ivLength)); + + // Encrypt the data + const encryptedData = await crypto.subtle.encrypt( + { + name: this.opts.algorithm, + iv, + }, + this.key, + data, + ); + + // Combine IV and encrypted data + const result = new Uint8Array(iv.length + encryptedData.byteLength); + result.set(iv); + result.set(new Uint8Array(encryptedData), iv.length); + + return result; + } + + async decrypt(payload: Uint8Array | string): Promise { + // Ensure payload is Uint8Array + if (typeof payload === 'string') { + throw new Error('Encrypted payload must be Uint8Array'); + } + + // Extract IV and encrypted data + const iv = payload.slice(0, this.opts.ivLength); + const encryptedData = payload.slice(this.opts.ivLength); + + // Decrypt the data + const decryptedData = await crypto.subtle.decrypt( + { + name: this.opts.algorithm, + iv, + }, + this.key, + encryptedData, + ); + + return new Uint8Array(decryptedData); + } +} diff --git a/packages/stack/src/utils/event-emitter.ts b/packages/stack/src/utils/event-emitter.ts new file mode 100644 index 0000000..62add96 --- /dev/null +++ b/packages/stack/src/utils/event-emitter.ts @@ -0,0 +1,146 @@ +// TODO: You'll probably want to use WeakRef's here. + +export type EventMap = Record; + +type Listener = (detail: T) => void; + +interface ListenerEntry { + listener: Listener; + wrappedListener: Listener; + debounceTime?: number; + once?: boolean; +} + +export class EventEmitter { + private listeners: Map>> = new Map(); + + on( + type: K, + listener: Listener, + debounceMilliseconds?: number, + ): Listener { + const wrappedListener = + debounceMilliseconds && debounceMilliseconds > 0 + ? this.debounce(listener, debounceMilliseconds) + : listener; + + if (!this.listeners.has(type)) { + this.listeners.set(type, new Set()); + } + + const listenerEntry: ListenerEntry = { + listener, + wrappedListener, + debounceTime: debounceMilliseconds, + }; + + this.listeners.get(type)?.add(listenerEntry as ListenerEntry); + return listener; + } + + once( + type: K, + listener: Listener, + debounceMilliseconds?: number, + ): Listener { + const wrappedListener: Listener = (detail: T[K]) => { + this.off(type, listener); + listener(detail); + }; + + const debouncedListener = + debounceMilliseconds && debounceMilliseconds > 0 + ? this.debounce(wrappedListener, debounceMilliseconds) + : wrappedListener; + + if (!this.listeners.has(type)) { + this.listeners.set(type, new Set()); + } + + const listenerEntry: ListenerEntry = { + listener, + wrappedListener: debouncedListener, + debounceTime: debounceMilliseconds, + once: true, + }; + + this.listeners.get(type)?.add(listenerEntry as ListenerEntry); + return listener; + } + + off(type: K, listener: Listener): void { + const listeners = this.listeners.get(type); + if (!listeners) return; + + const listenerEntry = Array.from(listeners).find( + (entry) => + entry.listener === listener || entry.wrappedListener === listener, + ); + + if (listenerEntry) { + listeners.delete(listenerEntry); + } + } + + emit(type: K, payload: T[K]): boolean { + const listeners = this.listeners.get(type); + if (!listeners) return false; + + listeners.forEach((entry) => { + entry.wrappedListener(payload); + }); + + return listeners.size > 0; + } + + removeAllListeners(): void { + this.listeners.clear(); + } + + async waitFor( + type: K, + predicate: (payload: T[K]) => boolean, + timeoutMs?: number, + ): Promise { + return new Promise((resolve, reject) => { + let timeoutId: ReturnType | undefined; + + const listener = (payload: T[K]) => { + if (predicate(payload)) { + // Clean up + this.off(type, listener); + if (timeoutId !== undefined) { + clearTimeout(timeoutId); + } + resolve(payload); + } + }; + + // Set up timeout if specified + if (timeoutMs !== undefined) { + timeoutId = setTimeout(() => { + this.off(type, listener); + reject(new Error(`Timeout waiting for event "${String(type)}"`)); + }, timeoutMs); + } + + this.on(type, listener); + }); + } + + private debounce( + func: Listener, + wait: number, + ): Listener { + let timeout: ReturnType; + + return (detail: T[K]) => { + if (timeout !== null) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + func(detail); + }, wait); + }; + } +} diff --git a/packages/wallet/src/utils/ext-error.ts b/packages/stack/src/utils/ext-error.ts similarity index 59% rename from packages/wallet/src/utils/ext-error.ts rename to packages/stack/src/utils/ext-error.ts index a56e1e5..0cf73e8 100644 --- a/packages/wallet/src/utils/ext-error.ts +++ b/packages/stack/src/utils/ext-error.ts @@ -1,17 +1,20 @@ -export class CashRPCError extends Error { - constructor(message: string, public readonly details?: string) { +export class ExtError extends Error { + constructor( + message: string, + public readonly details?: string, + ) { super(message); // Capture the stack trace (V8 engines only) // This step ensures the stack trace is captured correctly if (Error.captureStackTrace) { - Error.captureStackTrace(this, CashRPCError); + Error.captureStackTrace(this, ExtError); } else { this.stack = new Error().stack; console.error(this.stack); } // 👇️ because we are extending a built-in class - Object.setPrototypeOf(this, CashRPCError.prototype); + Object.setPrototypeOf(this, ExtError.prototype); } -} \ No newline at end of file +} diff --git a/packages/stack/src/utils/index.ts b/packages/stack/src/utils/index.ts new file mode 100644 index 0000000..b42cf82 --- /dev/null +++ b/packages/stack/src/utils/index.ts @@ -0,0 +1,6 @@ +export * from './encryption.js'; +export * from './event-emitter.js'; +export * from './ext-error.js'; +export * from './memoizer.js'; +export * from './misc.js'; +export * from './uuid.js'; diff --git a/packages/stack/src/utils/memoizer.ts b/packages/stack/src/utils/memoizer.ts new file mode 100644 index 0000000..1e8776a --- /dev/null +++ b/packages/stack/src/utils/memoizer.ts @@ -0,0 +1,55 @@ +// TODO: We may want to move this to @xocash/primitives. + +export class Memoizer { + private cache = new Map(); + + /** + * Gets a cached value for the given key, computing it if it doesn't exist. + * + * @param key - The key to look up or store the value under + * @param compute - Function to compute the value if it's not cached + * @returns The cached or computed value + */ + get(key: K, compute: () => V): V { + if (!this.cache.has(key)) { + this.cache.set(key, compute()); + } + return this.cache.get(key) as V; + } + + /** + * Checks if a value exists for the given key. + * + * @param key - The key to check + * @returns Whether the key exists in the cache + */ + has(key: string): boolean { + return this.cache.has(key); + } + + /** + * Deletes a cached value. + * + * @param key - The key to delete + * @returns Whether a value was deleted + */ + delete(key: string): boolean { + return this.cache.delete(key); + } + + /** + * Clears all cached values. + */ + clear(): void { + this.cache.clear(); + } + + /** + * Gets all cached keys. + * + * @returns Array of cached keys + */ + keys(): string[] { + return Array.from(this.cache.keys()); + } +} diff --git a/packages/stack/src/utils/misc.ts b/packages/stack/src/utils/misc.ts new file mode 100644 index 0000000..e706fb8 --- /dev/null +++ b/packages/stack/src/utils/misc.ts @@ -0,0 +1,17 @@ +import { binToHex } from '@bitauth/libauth'; + +// TODO: Put unsorted stuff in here and then try to sort them somewhere more sensible later. + +export function getOutpointId(outpoint: { + outpointTransactionHash: Uint8Array; + outpointIndex: number; +}): string { + const outpointHashHex = binToHex(outpoint.outpointTransactionHash); + + return `${outpointHashHex}:${outpoint.outpointIndex}`; +} + +// TODO: Jason uses form timestampMs_entityId +export function generateActivityId() { + return Date.now().toString(); +} diff --git a/packages/stack/src/utils/sse-client.ts b/packages/stack/src/utils/sse-client.ts new file mode 100644 index 0000000..f4665d7 --- /dev/null +++ b/packages/stack/src/utils/sse-client.ts @@ -0,0 +1,205 @@ +// TODO: This probably needs some work. +// The reason this exists is that Browser's native EventSource is rubbish: +// 1. It does not allow passing of headers. +// 2. It is browser-only (this implementation aims to work for Node also) +// 3. It does not handle disconnects/reconnects gracefully +// 4. It does not handle last-event-id (important for history). +// 5. It does not allow a custom fetch function (probably not necessary, but there's a reason I'd rather support this which I'll explain some other time). +// This might eventually be shifted out of XO/stack package and into its own. +// Q: "Why not just leverage an existing package?" +// A: They're all bad - the "go to" is a Microsoft (@azure/fetch-event-soruce) that is no longer maintained. +// It does not support NodeJS (or Workers) and we cannot depend upon forks of it. + +export interface SSEOptions { + fetch?: typeof fetch; + headers?: Record; + onMessage?: (event: SSEMessageEvent) => void; + onError?: (error: Error) => void; + onOpen?: () => void; + onClose?: () => void; + maxRetries?: number; + retryDelay?: number; +} + +export interface SSEMessageEvent { + data: string; + type: string; + id: string | null; + lastEventId: string; + origin: string; +} + +export interface SSEEvent { + data: string; + event: string; + id: string | null; +} + +export class SSEClient { + private url: string; + private options: Required> & { + headers: Record; + }; + private abortController: AbortController | null; + private retryCount: number; + private lastEventId: string; + private connected: boolean; + + constructor(url: string, options: SSEOptions = {}) { + this.url = url; + this.options = { + fetch: globalThis.fetch, + headers: {}, + onMessage: () => {}, + onError: () => {}, + onOpen: () => {}, + onClose: () => {}, + maxRetries: 5, + retryDelay: 3000, + ...options, + }; + + this.abortController = null; + this.retryCount = 0; + this.lastEventId = ''; + this.connected = false; + } + + public async connect(): Promise { + if (this.connected) { + return; + } + + this.abortController = new AbortController(); + + try { + const response = await this.options.fetch(this.url, { + headers: { + Accept: 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Last-Event-ID': this.lastEventId, + ...this.options.headers, + }, + signal: this.abortController.signal, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + if (!response.body) { + throw new Error('ReadableStream not supported'); + } + + this.connected = true; + this.options.onOpen(); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { value, done } = await reader.read(); + + if (done) { + this.handleDisconnect(); + break; + } + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split(/\r\n|\r|\n/); + + buffer = this.processEventStream(lines); + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + return; + } + + this.options.onError( + error instanceof Error ? error : new Error(String(error)), + ); + this.handleDisconnect(); + + if (this.retryCount < this.options.maxRetries) { + this.retryCount++; + setTimeout(() => this.connect(), this.options.retryDelay); + } + } + } + + private processEventStream(lines: string[]): string { + let event: SSEEvent = { + data: '', + event: 'message', + id: null, + }; + + for (let i = 0; i < lines.length - 1; i++) { + const line = lines[i]; + + if (line.length === 0) { + // Empty line means dispatch the event + if (event.data.length > 0) { + this.dispatchEvent(event); + event = { + data: '', + event: 'message', + id: null, + }; + } + continue; + } + + const [fieldName, ...fieldValue] = line.split(':'); + const value = fieldValue.join(':').trim(); + + switch (fieldName) { + case 'event': + event.event = value; + break; + case 'data': + event.data += value + '\n'; + break; + case 'id': + event.id = value; + this.lastEventId = value; + break; + case 'retry': { + const retryInt = parseInt(value, 10); + if (!isNaN(retryInt)) { + this.options.retryDelay = retryInt; + } + break; + } + } + } + + // Return the last incomplete line as the new buffer + return lines[lines.length - 1]; + } + + private dispatchEvent(event: SSEEvent): void { + const messageEvent: SSEMessageEvent = { + data: event.data.slice(0, -1), // Remove trailing newline + type: event.event, + id: event.id, + lastEventId: this.lastEventId, + origin: this.url, + }; + + this.options.onMessage(messageEvent); + } + + private handleDisconnect(): void { + this.connected = false; + this.options.onClose(); + } + + public close(): void { + if (this.abortController) { + this.abortController.abort(); + this.handleDisconnect(); + } + } +} diff --git a/packages/stack/src/utils/uuid.ts b/packages/stack/src/utils/uuid.ts new file mode 100644 index 0000000..64ba25e --- /dev/null +++ b/packages/stack/src/utils/uuid.ts @@ -0,0 +1,31 @@ +export function generateUUIDV7(customTimestamp?: bigint) { + // Step 1: Get the current timestamp in milliseconds + const timestamp = customTimestamp ?? BigInt(Date.now()); // Use BigInt to handle 48-bit values + + // Step 2: Convert the timestamp to a 48-bit hexadecimal string + const timestampHex = timestamp.toString(16).padStart(12, '0'); // Ensure 12 hex characters + + // Step 3: Generate 74 bits (19 hex characters) of random data + const randomHex = crypto + .getRandomValues(new Uint8Array(10)) + .map((b) => Number(b.toString(16).padStart(2, '0'))) + .join(''); // 10 bytes = 20 hex characters + + // Step 4: Combine fields to form the UUID + const version = '7'; // Version 7 (0111) + const variant = '8'; // Variant 1 (10xx) is "8", "9", "a", or "b" + + return ( + `${timestampHex.substring(0, 8)}-${timestampHex.substring(8, 12)}` + // Timestamp (48 bits) + `-${version}${randomHex.substring(0, 3)}` + // Version (4 bits) + 12 random bits + `-${variant}${randomHex.substring(3, 6)}` + // Variant (2 bits) + 14 random bits + `-${randomHex.substring(6)}` // Remaining 48 random bits + ); +} + +/** + * Extract the timestamp from a UUID v7 + */ +export function getTimestampFromUUID(uuid: string): bigint { + return BigInt(parseInt(uuid.slice(0, 8), 16)); +} diff --git a/packages/stack/src/wallet/activities.ts b/packages/stack/src/wallet/activities.ts new file mode 100644 index 0000000..8072961 --- /dev/null +++ b/packages/stack/src/wallet/activities.ts @@ -0,0 +1,240 @@ +// An activity represents an event that should be saved to persistent storage(s). +// TODO: Read more into LibAuth's concept of these. +// Jason mentions pruning, but I don't quite understand how that will be done yet. +// He also has the concept of "extensions" - but I'm not sure this has been fleshed out yet. + +import { generateUUIDV7, getTimestampFromUUID } from '../utils'; +import { BaseStore } from '../storage'; + +// TODO: Think about different activities +// TemplateActivity +// CashConnectActivity +// CoinLockActivity + +// TODO: StorageMany adapter should be configurable to make it so set() cannot fail. +// However, for optimization, get() should probably only retrieve from a primary (usually local) adapter. +// Otherwise, shit will be too slow if we're doing a get() or all() from a remote source. +// This may also solve the AES256 problem: Local does not need to be encrypted per se. +// Sync from Remote (encrypted) to Local (non-encrypted) can happen in the background. + +// TODO: I still haven't quite wrapped my head around LibAuth approach. +// But I imagine there'll be some kind of "Mutable" vs "Immutable" concept for Activities. +// Because we need to compile a "snapshot" based on Activities, if we add a Mutable Activity, we can prune previous Mutable Activities of the same identifier. +// This will help massively with optimization: +// 1. Our set of Activities becomes smaller (computation/storage = faster/better) +// 2. Syncing will take less time +// For the above, this probably means that we want to keep our Activities limited to one mutation. +// For example, we ideally wouldn't want an Activity that modifies/refers to multiple values. +// { type: 'freezeCoins', coins: [...] } +// ... because we cannot sync them efficiently (we need the entire history). +// Whereas, if we do: +// { type: 'freezeCoin' } +// ... and have an identifer, we can ensure that each wallet only syncs the latest frozen coin. +// Amendment 2024-11-20: +// For mutable, we could use property "unique: boolean". +// For priority (e.g. txTime activity), we could use property "policy: "earlist" | latest" + +// Type definitions +export type Activity = { + timestamp?: bigint; + type: Type; + id: string; + value: ValueType; +}; + +// Standardized Activities. +export type WalletName = Activity<'WalletName', string>; +export type WalletDescription = Activity<'WalletDescription', string>; +export type TransactionFirstSeen = Activity<'TransactionFirstSeen', string>; +export type TransactionMemo = Activity<'TransactionMemo', string>; + +// XO-specific Activities. +// TODO: These probably don't belong here and should be moved to the Wallet Codebase. +export type XOWalletColor = Activity<'XOWalletColor', string>; +export type XOTransactionCategory = Activity<'XOTransactionCategory', string>; +export type XOFrozenCoin = Activity<'XOFrozenCoin', boolean>; + +export type SnapshotByType = { + [type: string]: { + [id: string]: Activity; + }; +}; + +// Policy type definitions +export type PolicyFunction = ( + existingActivity: Activity | undefined, + newActivity: Activity, +) => Activity; + +export class Activities { + // TODO: Consider splitting into its own mutable class: ActivitySnapshot. + // This might be cleaner as we'll likely want to be able to view a raw timeline of activities. + // So we can make Activities have its own methods for that without cluttering the class. + public snapshot: SnapshotByType = {}; + private policies = new Map(); + + // Add a map to track store keys for each snapshot key + private storeKeyMap = new Map(); + + constructor(public store: BaseStore) { + this.store.on( + 'set', + async (payload: { key: string; value: Activity }) => { + // When a new activity is added, process it individually + await this.processActivity(payload.key, payload.value); + }, + ); + } + + /** + * Get all activities of a specific type + */ + public getActivitiesByType(type: string): Activity[] { + return Object.values(this.snapshot[type] || {}); + } + + /** + * Get a specific activity by type and id + */ + public getActivity( + type: string, + id: string, + ): Activity | undefined { + return this.snapshot[type]?.[id]; + } + + /** + * Process a single activity and update the snapshot + */ + private async processActivity( + storeKey: string, + activity: Activity, + ) { + if (!activity.timestamp) { + console.warn('Activity without timestamp encountered:', storeKey); + return; + } + + const snapshotKey = this.getSnapshotKey(activity); + const existingActivity = this.getActivityFromSnapshot( + activity.type, + activity.id, + ); + + // Apply policy to determine which activity to keep + const keptActivity = this.applyPolicy(existingActivity, activity); + + // Update snapshot with the kept activity + this.setActivityInSnapshot(keptActivity); + + if (keptActivity === activity) { + // If we kept the new activity, update the store key mapping + const oldStoreKey = this.storeKeyMap.get(snapshotKey); + if (oldStoreKey) { + // Delete the old activity from the store + await this.store.delete(oldStoreKey); + } + this.storeKeyMap.set(snapshotKey, storeKey); + } else { + // If we kept the existing activity, delete the new activity from the store + await this.store.delete(storeKey); + } + } + + /** + * Add a new activity to the store + */ + public async addActivity(activity: Activity) { + const timestamp = activity.timestamp ?? BigInt(Date.now()); + const activityWithTimestamp = { + ...activity, + timestamp, + }; + + const key = generateUUIDV7(timestamp); + await this.store.set(key, activityWithTimestamp); + // Note: We don't need to call processActivity here as the 'set' event will trigger it + } + + /** + * Compile a full snapshot from scratch (only needed for initialization or recovery) + */ + public async compileSnapshot() { + // Clear existing snapshot + this.snapshot = {}; + + // Get all activity data from our store + const activities = await this.store.all>(); + const entries = Object.entries(activities); + + // Sort activities by timestamp using UUID v7's embedded timestamp + const activitiesSorted = entries.sort(([keyA], [keyB]) => { + const timestampA = getTimestampFromUUID(keyA); + const timestampB = getTimestampFromUUID(keyB); + return Number(timestampA - timestampB); + }); + + // Process each activity in order + for (const [storeKey, activity] of activitiesSorted) { + await this.processActivity(storeKey, activity); + } + + return this.snapshot; + } + + /** + * Register a policy for handling conflicts between activities of the same type + * @param type Activity type to register policy for + * @param policyFn Function that decides which activity to keep + */ + public registerPolicy(type: string, policyFn: PolicyFunction) { + this.policies.set(type, policyFn); + } + + /** + * Get an activity from the snapshot by type and id + */ + private getActivityFromSnapshot( + type: string, + id: string, + ): Activity | undefined { + return this.snapshot[type]?.[id]; + } + + /** + * Set an activity in the snapshot, creating necessary object structure + */ + private setActivityInSnapshot(activity: Activity) { + if (!this.snapshot[activity.type]) { + this.snapshot[activity.type] = {}; + } + this.snapshot[activity.type][activity.id] = activity; + } + + /** + * Get the snapshot key for an activity + */ + private getSnapshotKey(activity: Activity): string { + return `${activity.type}:${activity.id}`; + } + + /** + * Apply policy to determine which activity should be kept + */ + private applyPolicy( + existingActivity: Activity | undefined, + newActivity: Activity, + ): Activity { + const policy = this.policies.get(newActivity.type); + + if (!policy) { + // Default policy: compare timestamps + if (!existingActivity) return newActivity; + return existingActivity.timestamp! >= newActivity.timestamp! + ? existingActivity + : newActivity; + } + + return policy(existingActivity, newActivity); + } +} diff --git a/packages/stack/src/wallet/base-wallet.ts b/packages/stack/src/wallet/base-wallet.ts new file mode 100644 index 0000000..2b894ae --- /dev/null +++ b/packages/stack/src/wallet/base-wallet.ts @@ -0,0 +1,154 @@ +import { + type InputTemplateBCH, + type TransactionTemplate, + type WalletAddresses, + type WalletTransactions, +} from './types.js'; + +import { type BlockchainUTXOs } from '../blockchain'; +import { type EventMap, EventEmitter } from '../utils'; + +import { type TokenBalances, type Address } from '@xocash/primitives'; + +/** + * Common interface for all wallet types. + * + * @remarks + * + * All function signatures are deliberately asynchronous to accommodate for cases where wallets might be remote. + * For example, we might eventually want to support a multisig service. + * Or we might have to support some other custodial "Apple Pay"-like OS-interface to get accepted onto their app store. + */ +export abstract class BaseWallet< + WalletEvents extends EventMap, +> extends EventEmitter { + /** + * Start monitoring this wallet for any transaction activity. + */ + abstract start(): Promise; + + /** + * Stop monitoring this wallet for any transaction activity. + */ + abstract stop(): Promise; + + /** + * Remove any event listeners from this wallet. + */ + abstract destroy(): Promise; + + /** + * Fetch all transactions from the blockchain involving this wallet. + * + * @remarks + * + * We will want to add a query argument so that workers can use this efficiently: + * + * ```ts + * { + * limit?: number; + * offset?: number; + * sort?: () => number; + * filter?: () => boolean; + * } + * ``` + * + * This "query" argument should be turned into a generic type as we will want to re-use it for Activities. + */ + abstract getTransactions(): Promise; + + /** + * Fetch all unspents from the blockchain belonging to this wallet. + */ + abstract getUnspents(): Promise; + + /** + * Fetch all unspents from the blockchain belonging to this wallet. + * + * @remarks + * + * I was considering removing this and just storing unspents as "UTXOSignable"s. + * But that might not be ideal if we want to have Watch-only wallets. + * A better approach might be to have Fetch Unspents return `Array` + * Still need to flesh that out. + * + * EDIT: We're going to want to store state for caching anyway... + * May as well just getUnspentDirectives() from the local state instead of fetch. + * Likewise for balances. + */ + abstract getUnspentDirectives(): Promise>; + + /** + * Get the satoshi balance of this wallet. + * + * @remarks + * + * Consider confirmed/unconfirmed balances (or some other representation). + * + * This becomes important in some instances because services like BitPay require one confirmation. + * This'll also have bearing on how we define our UTXO Selection strategies. + */ + abstract getBalanceSats(): Promise; + + /** + * Get the token balance of this wallet. + * + * @remarks + * + * Consider confirmed/unconfirmed balances (or some other representation). + * + * This becomes important in some instances because services like BitPay require one confirmation. + * This'll also have bearing on how we define our UTXO Selection strategies. + */ + abstract getBalanceTokens(): Promise; + + /** + * Get all addresses belonging to this wallet. + */ + abstract getAddresses(): Promise; + + /** + * Get the address that this wallet should receive to. + * + * @remarks + * + * Not all wallets will have an address (e.g. a HD Wallet), but should still be able to return an address that: + * a) Change will be sent to. + * b) Funds should be received to. + */ + abstract getReceivingAddress(): Promise
; + + /** + * Select UTXOs from this wallet to meet the amounts required. + * + * @remarks + * + * TODO: We are going to want to return the Transaction that was compiled. + * + * TODO: This should accept a second argument (probably function) that can filter/sory UTXOs. + * The intent here is to provide a "spending policy". E.g. Only use (or prioritise) UTXOs with 1-conf. + */ + abstract signTransaction( + template: Partial, + ): Promise; + + /** + * Select UTXOs from this wallet to meet the amounts required and broadcast it to the Blockchain. + * + * @remarks + * + * TODO: We are going to want to return the Transaction that was compiled. + */ + abstract sendTransaction( + template: Partial, + ): Promise; + + // TODO: This should be a push-only list. + // Need to understand how Jason's pruning approach works. + // It might make more sense to "key" activities by type: + // This would allow us to filter more effectively each time a new activity is added. + // As it stands, we would have to collate the entire list if it's done by timestamp. + abstract addActivity(activity: T): Promise; + + abstract export(): Promise; +} diff --git a/packages/wallet/src/wallet/index.ts b/packages/stack/src/wallet/index.ts similarity index 55% rename from packages/wallet/src/wallet/index.ts rename to packages/stack/src/wallet/index.ts index fdb1a3f..8430707 100644 --- a/packages/wallet/src/wallet/index.ts +++ b/packages/stack/src/wallet/index.ts @@ -1,3 +1,7 @@ +export * from './activities.js'; +export * from './base-wallet.js'; +export * from './transaction-builder.js'; +export * from './types.js'; export * from './wallet-hd.js'; export * from './wallet-p2pkh.js'; export * from './wallet-p2sh20.js'; diff --git a/packages/stack/src/wallet/transaction-builder.ts b/packages/stack/src/wallet/transaction-builder.ts new file mode 100644 index 0000000..dc335fe --- /dev/null +++ b/packages/stack/src/wallet/transaction-builder.ts @@ -0,0 +1,289 @@ +import { type WalletUTXO } from './types.js'; + +import { type UTXOSet, ExtMap, Outpoint } from '@xocash/primitives'; + +import { + type TransactionTemplateFixed, + type CompilerBCH, + type TransactionBCH, + binsAreEqual, + binToHex, + hexToBin, + encodeTransactionBCH, + encodeTransactionOutput, + generateTransaction, + getDustThreshold, +} from '@bitauth/libauth'; + +// TODO: This could probably still use a refactor, but isn't terrible anymore. +/** + * Transaction Builder that takes a template and can populate appropriate UTXOs. + * + * @remarks + * + * Note that this will only populate required Satoshis and Mutable Tokens. + * + * @experimental + */ +export class TransactionBuilder { + public unspents: Array; + + constructor( + public readonly transaction: TransactionTemplateFixed, + unspents: Array = [], + public sourceOutputs: UTXOSet = new ExtMap(), + ) { + // This class will "shift" (remove) unspents from the array so to ensure we don't mutate the source array, we clone it. + // NOTE: We cannot do a structuredClone here. It will not work in all cases. + this.unspents = [...unspents]; + } + + public calculateInputSats(): bigint { + return this.transaction.inputs.reduce((inputSats, input) => { + // Get the satoshi amount for this input. + // NOTE: If this is not a signable UTXO, we attempt to find the satoshi amount in the provided source outputs. + const valueSatoshis = + input.unlockingBytecode instanceof Uint8Array + ? this.sourceOutputs.getOrFail(Outpoint.fromInput(input).toString()) + .output.valueSatoshis + : input.unlockingBytecode.valueSatoshis; + + // Return the accumulated satoshi amount. + return (inputSats += valueSatoshis); + }, 0n); + } + + public calculateInputTokens(): Map { + // NOTE: We cannot index by Uint8Array, so we convert the category to hexadecimal form. + const tokensRequired: Map = new Map(); + + // Iterate over each input so that we can calculate the total required for each token category. + this.transaction.inputs.forEach((input) => { + // Get the source output for this input. + // NOTE: If this is not a signable UTXO, we attempt to find the source output in the provided source outputs. + const sourceOutput = + input.unlockingBytecode instanceof Uint8Array + ? this.sourceOutputs.getOrFail(Outpoint.fromInput(input).toString()) + .output + : input.unlockingBytecode; + + // If the source output contains fungible tokens... + if (sourceOutput.token && !sourceOutput.token.nft) { + // Convert the token category to hex so we can index it in a map. + const categoryHex = binToHex(sourceOutput.token.category); + + // Get the current total for the given category. + const currentTokenTotal = tokensRequired.get(categoryHex) || 0n; + + // Calculate the new total for the given category. + const newTotal = currentTokenTotal + sourceOutput.token.amount; + + // Set the new total for the given category. + tokensRequired.set(categoryHex, newTotal); + } + }); + + return tokensRequired; + } + + public calculateOutputSats(): bigint { + return this.transaction.outputs.reduce( + (outputTotal, output) => (outputTotal += output.valueSatoshis), + 0n, + ); + } + + public calculateOutputTokens(): Map { + // NOTE: We must use a Map (as opposed to object) if we're indexing by Uint8Array. + const tokensRequired: Map = new Map(); + + this.transaction.outputs.forEach((output) => { + // If this output has a token and it is not an NFT... + if (output.token && !output.token.nft) { + const currentTokenTotal = + tokensRequired.get(binToHex(output.token.category)) || 0n; + const newTotal = currentTokenTotal + output.token.amount; + tokensRequired.set(binToHex(output.token.category), newTotal); + } + }); + + return tokensRequired; + } + + public calculateFeeSats(): bigint { + return BigInt(this.toBytes().length); + } + + public calculateRequiredSats(): bigint { + const outputSats = this.calculateOutputSats(); + const feeSats = this.calculateFeeSats(); + const requiredSats = feeSats + outputSats; + + return requiredSats; + } + + public addTokensFromUnspents(): TransactionBuilder { + // Calculate the tokens required for this transaction. + const tokensRequired = this.calculateOutputTokens(); + + // Interate through each token and add inputs to meet the required amount. + tokensRequired.forEach((requiredAmountForToken, categoryId) => { + // Find unspents containing this category ID. + const unspentsContainingToken = this.unspents.filter((unspent) => + binsAreEqual( + unspent.unlockingBytecode.token?.category || new Uint8Array(), + hexToBin(categoryId), + ), + ); + + // Sort them so that we prioritize the largest amounts first. + const unspentsContainingTokenSorted = unspentsContainingToken.sort( + (a, b) => + Number(b.unlockingBytecode.token?.amount || 0) - + Number(a.unlockingBytecode.token?.amount || 0), + ); + + // Loop until we have enough input tokens to match the required amount of output tokens. + while ( + (this.calculateInputTokens().get(categoryId) || 0n) < + requiredAmountForToken + ) { + // Get the unspent to add from the top of our list and remove it. + const unspent = unspentsContainingTokenSorted.shift(); + + // If there are no more unspents, it means we have insufficient funds, so throw an error. + if (!unspent) { + throw new Error( + `Failed to select UTXOs: ${ + this.calculateInputTokens().get(categoryId) || 0n + } of token (${categoryId}) available, ${requiredAmountForToken} required`, + ); + } + + // Append this Input. + this.transaction.inputs.push(unspent); + } + }); + + return this; + } + + public addSatoshisFromUnspents(): TransactionBuilder { + // Find unspents without tokens. + const unspentsWithoutTokens = this.unspents.filter( + (unspent) => typeof unspent.unlockingBytecode.token === 'undefined', + ); + + // Sort them so that we prioritize the largest amounts first. + const unspentsWithoutTokensSorted = unspentsWithoutTokens.sort((a, b) => + Number( + b.unlockingBytecode.valueSatoshis - a.unlockingBytecode.valueSatoshis, + ), + ); + + // Iterate over each unspent until we get the amount desired. + while ( + this.calculateInputSats() < + this.calculateOutputSats() + this.calculateFeeSats() + ) { + // Get the unspent to add from the top of our list and remove it. + const unspent = unspentsWithoutTokensSorted.shift(); + + // If there are no more unspents, it means we have insufficient funds, so throw an error. + if (!unspent) { + throw new Error( + `Failed to select UTXOs: ${this.calculateInputSats()} of sats available, ${this.calculateRequiredSats()} required`, + ); + } + + // Append this unspent. + this.transaction.inputs.push(unspent); + } + + return this; + } + + public addTokenChangeOutputs( + changeLockingBytecode: Uint8Array, + ): TransactionBuilder { + // Calculate input and output token totals. + const inputTokens = this.calculateInputTokens(); + const outputTokens = this.calculateOutputTokens(); + + // Iterate over each input and add a token change output (if there is change). + inputTokens.forEach((amount, categoryId) => { + const inputAmount = amount; + const outputAmount = outputTokens.get(categoryId) || 0n; + const changeAmount = inputAmount - outputAmount; + + // Add the change output. + this.transaction.outputs.push({ + lockingBytecode: changeLockingBytecode, + valueSatoshis: 1000n, + token: { + category: hexToBin(categoryId), + amount: changeAmount, + }, + }); + }); + + return this; + } + + public addSatoshiChangeOutput( + changeLockingBytecode: Uint8Array, + ): TransactionBuilder { + // NOTE: This might lead to over-paying the tx fee slightly. + const changeOutputTemplate = { + lockingBytecode: changeLockingBytecode, + valueSatoshis: 0n, + }; + const changeOutputFee = + BigInt(encodeTransactionOutput(changeOutputTemplate).length) + 4n; + + // Calculate the input satoshis and the current transaction fee. + const inputSats = this.calculateInputSats(); + const outputSats = this.calculateOutputSats(); + const transactionFee = this.calculateFeeSats() + changeOutputFee; + + // Calculate the change satoshis. + const changeSats = inputSats - (outputSats + transactionFee); + + // If the Change Output would be less than the dust threshold, do nothing. + if (changeSats < getDustThreshold(changeOutputTemplate)) { + return this; + } + + // Add the change output. + this.transaction.outputs.push({ + lockingBytecode: changeLockingBytecode, + valueSatoshis: changeSats, + }); + + // Return transaction with Satoshi Change Output. + return this; + } + + public generate(): TransactionBCH { + // Generate the transaction + const generatedTransaction = generateTransaction(this.transaction); + + // Ensure the transaction generated successfully. + if (!generatedTransaction.success) { + // lmao but better to show it to help devs. + const errors = generatedTransaction.errors + .map((error) => error.errors.map((error) => error.error).join(',')) + .join(','); + + throw new Error(`Failed to generate transaction: ${errors}`); + } + + return generatedTransaction.transaction; + } + + public toBytes(): Uint8Array { + const generatedTransaction = this.generate(); + + return encodeTransactionBCH(generatedTransaction); + } +} diff --git a/packages/stack/src/wallet/types.ts b/packages/stack/src/wallet/types.ts new file mode 100644 index 0000000..2a3216b --- /dev/null +++ b/packages/stack/src/wallet/types.ts @@ -0,0 +1,51 @@ +import { type BaseBlockchain, type BlockchainTransaction } from '../blockchain'; +import { type BaseStore } from '../storage'; + +import { Address, ExtMap } from '@xocash/primitives'; + +import { + type InputTemplate, + type CompilerBCH, + type CompilationData, + type CompilationDirectiveUnlocking, + type TransactionTemplateFixed, + type Output, +} from '@bitauth/libauth'; + +export type WalletServices = { + blockchain: BaseBlockchain; + store?: BaseStore; + cache?: BaseStore; +}; + +export type InputTemplateBCH = InputTemplate< + CompilerBCH, + false, + CompilationData +>; + +// TODO: Rename me to WalletUTXO +export type WalletUTXO = { + outpointIndex: number; + outpointTransactionHash: Uint8Array; + sequenceNumber: number; + unlockingBytecode: CompilationDirectiveUnlocking< + CompilerBCH, + CompilationData + >; +}; + +export type TransactionTemplate = TransactionTemplateFixed; + +export type FilterAndSortCallback = () => Promise>; + +export type IWalletActivities = { [id: string]: T }; + +// NOTE: We index this by Lockscript Hex so that we can find CashAddr, Legacy or other. +export type WalletAddresses = ExtMap
; + +export type WalletTransaction = BlockchainTransaction & { + sourceOutputs: Array; +}; + +export type WalletTransactions = ExtMap; diff --git a/packages/stack/src/wallet/wallet-hd.ts b/packages/stack/src/wallet/wallet-hd.ts new file mode 100644 index 0000000..efddd9e --- /dev/null +++ b/packages/stack/src/wallet/wallet-hd.ts @@ -0,0 +1,523 @@ +// Import the Stamp class to derive the private keys +import { BaseWallet } from './base-wallet.js'; +import { type TransactionTemplate } from './types.js'; +import { TransactionBuilder } from './transaction-builder.js'; +import { type WalletServices } from './types.js'; +import { + WalletP2PKH, + WalletP2PKHFactory, + useWalletP2PKH, +} from './wallet-p2pkh.js'; + +import { + type BaseBlockchain, + type BlockchainTransactions, + type BlockchainUTXOs, +} from '../blockchain'; +import { + type BaseStore, + type StoreValueProxy, + StoreInMemory, +} from '../storage'; + +import { + type TokenBalances, + HDPrivateNode, + ExtMap, + calculateBalanceSats, + calculateBalanceTokens, + encodeExtendedJson, +} from '@xocash/primitives'; + +import { hexToBin } from '@bitauth/libauth'; + +export const DERIVATION_PATH = "m/44'/145'/0'"; +export const ADDRESS_GAP = 20; + +// These constants correspond to the "change" path in BIP44. +// See: https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki +export const CHAIN_EXTERNAL = 0; +export const CHAIN_INTERNAL = 1; + +export type WalletHDGenesisData = { + type: 'WalletHD'; + derivationPath: string; + addressGap?: number; +} & ({ seed: string } | { mnemonic: string } | { xpriv: string }); + +export type WalletHDEvents = { + walletDataUpdated: WalletHDGenesisData; + shouldMonitorUpdated: boolean; + walletsUpdated: Array; + transactionsUpdated: BlockchainTransactions; + unspentsUpdated: BlockchainUTXOs; + stateUpdated: string | null; +}; + +/** + * A Hierarhical Determinstic (BIP44) Wallet. + * + * @example + * + * ```ts + * // Define services to use. + * const services = { + * blockchain, + * cache, + * storage, + * } + * + * // Instantiate an instance of WalletHD. + * const walletHD = await WalletHD.from({ + * derivationPath: "m/44'/145'/0'", + * mnemonic: 'page pencil stock planet limb cluster assault speak off joke private pioneer' + * // or "seed" or "xpub" + * }, services); + * + * // Scan for transactions and start monitoring. + * await wallet = await walletHD.start(); + * ``` + */ +export class WalletHD< + WalletType extends WalletP2PKH = WalletP2PKH, +> extends BaseWallet> { + // Mutable State. + public shouldMonitor = false; + + // Store values that are proxied for convenience/clarity. + // NOTE: We define these for clarity so that we can see the state stored with this wallet. + public genesisData: StoreValueProxy; + + // Store child wallets, grouped by chain ("change") path in BIP44 spec. + public wallets: { [chainPath: number]: Array } = {}; + + constructor( + public blockchain: BaseBlockchain, + public store: BaseStore, + public cache: BaseStore, + public hdPrivateNode: HDPrivateNode, + private walletFactory: WalletP2PKHFactory = useWalletP2PKH, + ) { + super(); + + // Initialize empty arrays for our wallets. + this.wallets[CHAIN_EXTERNAL] = []; + this.wallets[CHAIN_INTERNAL] = []; + + // Initialize proxies to our stored data. + this.genesisData = this.store.proxy('genesisData'); + } + + static async from( + this: T, + services: WalletServices, + genesisData?: WalletHDGenesisData, + walletFactory?: WalletP2PKHFactory, + ) { + // Assign in memory stores as defaults in case otherwise not provided. + const servicesFinal = { + ...{ + store: new StoreInMemory(), + cache: new StoreInMemory(), + }, + ...services, + }; + + // If we are attempting to create a new wallet... + if (genesisData) { + // Ensure that the provided store does not already contain data. + if (Object.keys(await servicesFinal.store.all()).length) { + throw new Error(`Provided store already contains data`); + } + + // Store the wallet genesis data. + await servicesFinal.store.set( + 'genesis', + genesisData, + ); + } + + // Retrieve our genesis data and extract the private key. + const retrievedGenesisData = + await servicesFinal.store.getOrFail('genesis'); + + let hdPrivateNode: HDPrivateNode; + + // Instantiate the HDPrivateNode from the type specified in metadata. + if ('seed' in retrievedGenesisData) { + hdPrivateNode = HDPrivateNode.fromSeed( + hexToBin(retrievedGenesisData.seed), + ); + } else if ('mnemonic' in retrievedGenesisData) { + hdPrivateNode = HDPrivateNode.fromMnemonic(retrievedGenesisData.mnemonic); + } else { + hdPrivateNode = HDPrivateNode.fromXPriv(retrievedGenesisData.xpriv); + } + + // Return a new instance of WalletHD. + return new this( + servicesFinal.blockchain, + servicesFinal.store, + servicesFinal.cache, + hdPrivateNode, + walletFactory, + ) as InstanceType; + } + + async start() { + // Get the genesis data. + const genesisData = await this.genesisData.getOrFail(); + + // Get the address gap. + const addressGap = genesisData.addressGap || ADDRESS_GAP; + + // Scan the HDWallet for child wallets. + await this.scan(0, addressGap); + + // Declare storage for our start promises. + const startPromises: Promise[] = []; + + // Iterate over each chain path and start each wallet. + for (const chainWallets of Object.values(this.wallets)) { + for (const wallet of chainWallets) { + startPromises.push(wallet.start()); + } + } + + // Wait for all of our start promises to complete. + await Promise.all(startPromises); + } + + async stop() { + // Declare storage for our stop promises. + const stopPromises: Promise[] = []; + + // Iterate over each chain path and stop each wallet. + for (const chainWallets of Object.values(this.wallets)) { + for (const wallet of chainWallets) { + stopPromises.push(wallet.stop()); + } + } + + // Wait for all of our stop promises to complete. + await Promise.all(stopPromises); + } + + async destroy() { + this.removeAllListeners(); + } + + async getTransactions() { + // Declare storage for our fetch promises. + const fetchPromises = []; + + // Iterate over each chain path and fetch transactions for each wallet. + for (const chainWallets of Object.values(this.wallets)) { + for (const wallet of chainWallets) { + fetchPromises.push(wallet.getTransactions()); + } + } + + // Wait for all of our fetch promises to complete. + const txs = await Promise.all(fetchPromises); + + // Flatten (and deduplicate) the transactions. + const txsFlattened = ExtMap.flatten(txs); + + // Emit an event to notify that transactions have been updated. + this.emit('transactionsUpdated', txsFlattened); + + // Return the deduplicated transactions. + return txsFlattened; + } + + async getUnspents() { + // Declare storage for our fetch promises. + const fetchPromises = []; + + // Iterate over each chain path and fetch unspents for each wallet. + for (const chainWallets of Object.values(this.wallets)) { + for (const wallet of chainWallets) { + fetchPromises.push(wallet.getUnspents()); + } + } + + // Wait for all of our fetch promises to complete. + const unspents = await Promise.all(fetchPromises); + + // Flatten them. + const unspentsFlattened = ExtMap.flatten(unspents); + + // Return the unspents. + return unspentsFlattened; + } + + async getUnspentDirectives() { + // Declare storage for our fetch promises. + const fetchPromises = []; + + // Iterate over each chain path and fetch unspent directives for each wallet. + for (const chainWallets of Object.values(this.wallets)) { + for (const wallet of chainWallets) { + fetchPromises.push(wallet.getUnspentDirectives()); + } + } + + // Wait for all of our fetch promises to complete. + const unspentDirectives = await Promise.all(fetchPromises); + + // Flatten them. + const unspentsFlattened = unspentDirectives.flat(); + + // Return the unspents. + return unspentsFlattened; + } + + async getBalanceSats() { + // Fetch our unspents. + const unspents = await this.getUnspents(); + + // Get the individual UTXOs. + const utxos = unspents + .map((blockchainUTXO) => blockchainUTXO.utxo) + .toArray(); + + // Calculate the balance. + return calculateBalanceSats(utxos); + } + + async getBalanceTokens(): Promise { + // Fetch our unspents. + const unspents = await this.getUnspents(); + + // Get the individual UTXOs. + const utxos = unspents + .map((blockchainUTXO) => blockchainUTXO.utxo) + .toArray(); + + // Calculate the token balances. + return calculateBalanceTokens(utxos); + } + + async getAddresses() { + const chainPaths = [CHAIN_EXTERNAL, CHAIN_INTERNAL]; + + let addressPromises = []; + + for (const chainPath of chainPaths) { + addressPromises.push( + ...this.wallets[chainPath].map((wallet) => + wallet.getReceivingAddress(), + ), + ); + } + + const addresses = await Promise.all(addressPromises); + + return ExtMap.fromArray(addresses, (address) => address.toLockscriptHex()); + } + + async getReceivingAddress(chainPath = CHAIN_EXTERNAL) { + // TODO: Derive a new wallet, not the first one you lazy fuckhead. + // You'll probably need to refactor your scan() approach. + let receivingWallet = this.wallets[chainPath][0]; + + // If there is no receiving wallet, create one. + // TODO: This is fucked. You need to rethink deriveWallets. + if (!receivingWallet) { + this.deriveWallets(1, chainPath); + + receivingWallet = this.wallets[chainPath][0]; + } + + // Return the receiving address of the derived wallet. + return receivingWallet.privateKey.derivePublicKey().deriveAddress(); + } + + async signTransaction( + txTemplate: Partial, + ): Promise { + // Populate the final transaction template by merging with defaults. + // NOTE: This is just a convenience to make "send" easier to use. + const txTemplateFinal: TransactionTemplate = { + ...{ + version: 2, + locktime: 0, + inputs: [], + outputs: [], + }, + ...txTemplate, + }; + + // Get the receiving address of this wallet. + // NOTE: As per BIP44 spec, we set the change address to use the internal chain. + const changeAddress = await this.getReceivingAddress(CHAIN_EXTERNAL); + + // Get the change locking bytecode (change address) for this wallet. + const changeLockingBytecode = changeAddress.toLockscriptBytes(); + + // Fetch unspents that are usable by this wallet. + const unspents = await this.getUnspentDirectives(); + + // Create an instance of our transaction builder and populate with our Wallet's UTXOs. + // NOTE: We must deal with tokens first and satoshis last. + // The reason is that the change output will be determined based on transaction fee. + const txBuilder = new TransactionBuilder(txTemplateFinal, unspents) + .addTokensFromUnspents() + .addTokenChangeOutputs(changeLockingBytecode) + .addSatoshisFromUnspents() + .addSatoshiChangeOutput(changeLockingBytecode); + + // Generate the transaction. + const generatedTxBytes = txBuilder.toBytes(); + + return generatedTxBytes; + } + + async sendTransaction(txTemplate: Partial) { + // Sign the transaction template. + const generatedTxBytes = await this.signTransaction(txTemplate); + + // Broadcast the transaction to the blockchain. + await this.blockchain.broadcastTransaction(generatedTxBytes); + } + + async addActivity(activity: T) { + // Generate an Activity ID. + // TODO: Look more into how LibAuth intends to do this. + const activityId = Date.now().toString(); + + // Store the data. + await this.store.set(activityId, activity); + } + + async export() { + const allData = await this.store.all(); + + const asJson = encodeExtendedJson(allData); + + return asJson; + } + + async deriveWallets( + count: number, + chainPath = CHAIN_EXTERNAL, + startIndex = 0, + ) { + // Get the derivation path from the genesis data. + const genesisData = await this.genesisData.getOrFail(); + + // Create an array to store our wallets. + const newWallets: Array = []; + + // Create the given count of wallets. + for (let i = 0; i < count; i++) { + // Define the derivation path. + const derivationPath = `${genesisData.derivationPath}/${chainPath}/${startIndex + i}`; + + // Derive the private key at the given path. + const privateKeyHex = await this.cache.getOrSet(derivationPath, () => { + const childNode = this.hdPrivateNode.derivePath(derivationPath); + return childNode.privateKey().toHex(); + }); + + // Create a child P2PKH wallet for this Private Key. + const wallet = await this.walletFactory( + { + blockchain: this.blockchain, + cache: this.cache, + }, + { + type: 'WalletP2PKH', + privateKey: privateKeyHex, + }, + ); + + // Subscribe to wallet-state updates. + wallet.on('stateUpdated', async () => { + this.emit('stateUpdated', null); + }); + + // Start monitoring the wallet. + wallet.start(); + + // Add the wallet to our list of wallets. + newWallets.push(wallet); + } + + // Set our wallets. + this.wallets[chainPath] = [...this.wallets[chainPath], ...newWallets]; + + // Return the wallets. + return newWallets; + } + + async scan( + startIndex = 0, + addressGap = 20, + chainPaths: Array = [CHAIN_EXTERNAL, CHAIN_INTERNAL], + ) { + // Iterate over each of the provided chain (change) paths. + for (const chainPath of chainPaths) { + // Array to store active wallet nodes + const wallets: Array = []; + + let currentIndex = startIndex; + let emptyAddressCount = 0; + + // Continue scanning until we find 'addressGap' consecutive empty addresses + while (emptyAddressCount < addressGap) { + // Derive a number of wallets equivalent to our addressGap. + const batch: Array = await this.deriveWallets( + addressGap, + chainPath, + currentIndex, + ); + + // Get the transaction history of each wallet in the batch + const results = await Promise.all( + batch.map((wallet) => wallet.getTransactions()), + ); + + // Process the results + for (let i = 0; i < results.length; i++) { + if (results[i].size > 0) { + // This address has transactions, add it to nodes and reset empty address count + wallets.push(batch[i]); + emptyAddressCount = 0; + } else { + // This address is empty, increment the empty address count + emptyAddressCount++; + } + + currentIndex++; + + // If we've found 'addressGap' consecutive empty addresses, stop scanning + if (emptyAddressCount >= addressGap) { + break; + } + } + } + } + + this.emit('stateUpdated', null); + } +} + +//----------------------------------------------------------------------------- +// Factories Types/Functions +//----------------------------------------------------------------------------- + +export type WalletHDFactory = ( + services: WalletServices, + genesisData?: WalletHDGenesisData, + walletFactory?: WalletP2PKHFactory, +) => Promise; + +export async function useWalletHD( + services: WalletServices, + genesisData?: WalletHDGenesisData, + walletFactory?: WalletP2PKHFactory, +): Promise { + return (await WalletHD.from(services, genesisData, walletFactory)) as T; +} diff --git a/packages/stack/src/wallet/wallet-p2pkh.ts b/packages/stack/src/wallet/wallet-p2pkh.ts new file mode 100644 index 0000000..d1e7a39 --- /dev/null +++ b/packages/stack/src/wallet/wallet-p2pkh.ts @@ -0,0 +1,450 @@ +import { BaseWallet } from './base-wallet.js'; +import { type WalletTransactions, type TransactionTemplate } from './types.js'; +import { TransactionBuilder } from './transaction-builder.js'; +import { type WalletServices } from './types.js'; + +import { + type BaseBlockchain, + type BlockchainTransaction, + type BlockchainUTXOs, +} from '../blockchain'; +import { type BaseStore, StoreInMemory } from '../storage'; + +import { + Address, + ExtMap, + PrivateKey, + calculateBalanceSats, + calculateBalanceTokens, + encodeExtendedJson, +} from '@xocash/primitives'; + +import { + type Input, + binToHex, + sha256, + utf8ToBin, + walletTemplateP2pkhNonHd, + walletTemplateToCompilerBCH, +} from '@bitauth/libauth'; + +export type WalletP2PKHGenesisData = { + type: 'WalletP2PKH'; + privateKey: string; +}; + +export type WalletP2PKHEvents = { + transactionsUpdated: WalletTransactions; + unspentsUpdated: BlockchainUTXOs; + stateUpdated: string | null; +}; + +/** + * @example + * ```ts + * // Instantiate P2PKH Wallet. + * const walletP2PKH = WalletP2PKH.fromWIF('someWif'); + * + * // Start monitoring the balance. + * await walletP2PKH.startMonitoring(); + * ``` + */ +export class WalletP2PKH extends BaseWallet { + // Internal State. + public isStarted = false; + public status: string | null = null; + + constructor( + // Dependencies. + public blockchain: BaseBlockchain, + // Persistent Storage. + public store: BaseStore, + // Cache. + public cache: BaseStore, + // Properties. + public privateKey: PrivateKey, + ) { + super(); + } + + static async from( + this: T, + services: WalletServices, + genesisData?: WalletP2PKHGenesisData, + ) { + // Assign in memory stores as defaults in case otherwise not provided. + const servicesFinal = { + ...{ + store: new StoreInMemory(), + cache: new StoreInMemory(), + }, + ...services, + }; + + // If we are attempting to create a new wallet... + if (genesisData) { + // Ensure that the provided store does not already contain data. + if (await servicesFinal.store.get('genesis')) { + throw new Error(`Provided store already contains data`); + } + + // Store the wallet genesis data. + await servicesFinal.store.set( + 'genesis', + genesisData, + ); + } + + // Retrieve our genesis data and extract the private key. + const retrievedGenesisData = + await servicesFinal.store.getOrFail('genesis'); + + // Get the Private Key as hex. + const privateKey = PrivateKey.fromHex(retrievedGenesisData.privateKey); + + // Return a new instance of Wallet P2PKH. + return new this( + servicesFinal.blockchain, + servicesFinal.store, + servicesFinal.cache, + privateKey, + ) as InstanceType; + } + + static fromBytes( + this: T, + services: WalletServices, + privateKeyBytes: Uint8Array, + ) { + // Create a Private Key entity from the provided bytes. + const privateKey = PrivateKey.fromBytes(privateKeyBytes); + + // Return a new instance of Wallet P2PKH. + return this.from(services, { + type: 'WalletP2PKH', + privateKey: privateKey.toHex(), + }); + } + + static fromHex( + this: T, + services: WalletServices, + privateKeyHex: string, + ) { + // Create a Private Key entity from the provided bytes. + const privateKey = PrivateKey.fromHex(privateKeyHex); + + // Return a new instance of Wallet P2PKH. + return this.from(services, { + type: 'WalletP2PKH', + privateKey: privateKey.toHex(), + }); + } + + static fromWIF( + this: T, + services: WalletServices, + privateKeyWif: string, + ) { + // Create a Private Key entity from the provided bytes. + const privateKey = PrivateKey.fromWIF(privateKeyWif); + + // Return a new instance of Wallet P2PKH. + return this.from(services, { + type: 'WalletP2PKH', + privateKey: privateKey.toHex(), + }); + } + + static generateRandom( + this: T, + services: WalletServices, + ) { + // Create a Private Key entity generated from random bytes. + const privateKey = PrivateKey.generateRandom(); + + // Return a new instance of Wallet P2PKH. + return this.from(services, { + type: 'WalletP2PKH', + privateKey: privateKey.toHex(), + }); + } + + async getId() { + const genesisData = await this.store.getOrFail('genesis'); + + return sha256.hash(utf8ToBin(JSON.stringify(genesisData))); + } + + async start() { + // Do nothing if already started. + if (this.isStarted) { + return; + } + + // Get the receiving address of this wallet so we can monitor it. + const receivingAddress = await this.getReceivingAddress(); + + // Start monitoring the receiving address. + await this.blockchain.subscribeAddress( + receivingAddress.toCashAddr(), + this.onAddressNotification.bind(this), + ); + + // Mark this wallet as started. + this.isStarted = true; + } + + async stop() { + // Do nothing if not started. + if (!this.isStarted) { + return; + } + + // Get the receiving address of this wallet so we can stop monitoring it. + const receivingAddress = await this.getReceivingAddress(); + + // Stop monitoring the receiving address. + await this.blockchain.unsubscribeAddress( + receivingAddress.toCashAddr(), + this.onAddressNotification.bind(this), + ); + + // Mark this wallet as stopped. + this.isStarted = false; + } + + async destroy() { + this.removeAllListeners(); + } + + async getTransactions() { + // Get this node's address. + const address = await this.getReceivingAddress(); + + // Get transactions involving this address. + const transactions = await this.blockchain.fetchTransactions( + address.toCashAddr(), + ); + + // Define a function to get a single source output for an input. + const getSourceOutput = async (input: Input) => { + const sourceTx = await this.blockchain.fetchTransaction( + binToHex(input.outpointTransactionHash), + ); + + const sourceOutput = sourceTx.getOutputs().at(input.outpointIndex); + + if (!sourceOutput) { + throw new Error(`Failed to find source output`); + } + + return sourceOutput; + }; + + // Define a function to get all source outputs for a tx. + const getSourceOutputs = async (tx: BlockchainTransaction) => { + const sourceOutputPromises = tx.transaction + .getInputs() + .map((input) => getSourceOutput(input)); + + return await Promise.all(sourceOutputPromises); + }; + + // Get the source outputs for each of the transactions. + const walletTxs = await Promise.all( + transactions.toArray().map(async (tx) => ({ + ...tx, + sourceOutputs: await getSourceOutputs(tx), + })), + ); + + // Wait for source outputs to be populated. + const walletTxsIndexed = ExtMap.fromArray(walletTxs, (tx) => + tx.hash.toHex(), + ); + + // TODO: Merge activity information into a WalletTransaction type. + // 2024-11-17: Actually, that might not belong here? + + // Emit an event to notify that transactions have been updated. + this.emit('transactionsUpdated', walletTxsIndexed); + + // Return the fetched transactions. + return walletTxsIndexed; + } + + async getUnspents() { + // Get this wallet's address. + const address = await this.getReceivingAddress(); + + // Fetch unspents belonging to this address. + const unspents = await this.blockchain.fetchUnspents(address.toCashAddr()); + + // TODO: Merge activity information into a WalletUnspent type. + + // Emit an event to notify that unspents have been updated. + this.emit('unspentsUpdated', unspents); + + // Return the unspents. + return unspents; + } + + async getUnspentDirectives() { + // Get a list of unspent outputs. + const unspentOutputs = await this.getUnspents(); + + // Create our P2PKH Compiler. + const compilerP2PKH = walletTemplateToCompilerBCH(walletTemplateP2pkhNonHd); + + // Transform our inputs into unlocking directives. + const inputDirectives = unspentOutputs + .map((blockchainUTXO) => ({ + outpointIndex: blockchainUTXO.utxo.outpoint.outpointIndex, + outpointTransactionHash: + blockchainUTXO.utxo.outpoint.outpointTransactionHash, + sequenceNumber: 0, + unlockingBytecode: { + compiler: compilerP2PKH, + data: { + keys: { privateKeys: { key: this.privateKey.toBytes() } }, + }, + script: 'unlock', + token: blockchainUTXO.utxo.output.token, + valueSatoshis: blockchainUTXO.utxo.output.valueSatoshis, + }, + })) + .toArray(); + + // Return the directives. + return inputDirectives; + } + + async getBalanceSats() { + // Fetch our unspents. + const unspents = await this.getUnspents(); + + // Get the individual UTXOs. + const utxos = unspents + .map((blockchainUTXO) => blockchainUTXO.utxo) + .toArray(); + + // Calculate the balance. + return calculateBalanceSats(utxos); + } + + async getBalanceTokens() { + // Fetch our unspents. + const unspents = await this.getUnspents(); + + // Get the individual UTXOs. + const utxos = unspents + .map((blockchainUTXO) => blockchainUTXO.utxo) + .toArray(); + + // Calculate the token balances. + return calculateBalanceTokens(utxos); + } + + async getAddresses() { + const addresses = await Promise.all([this.getReceivingAddress()]); + + return ExtMap.fromArray(addresses, (address) => address.toLockscriptHex()); + } + + async getReceivingAddress() { + return this.privateKey.derivePublicKey().deriveAddress(); + } + + async signTransaction(txTemplate: Partial) { + // Populate the final transaction template by merging with defaults. + // NOTE: This is just a convenience to make "send" easier to use. + const txTemplateFinal: TransactionTemplate = { + ...{ + version: 2, + locktime: 0, + inputs: [], + outputs: [], + }, + ...txTemplate, + }; + + // Get the receiving address of this wallet. + const receivingAddress = await this.getReceivingAddress(); + + // Get the change locking bytecode (change address) for this wallet. + const changeLockingBytecode = receivingAddress.toLockscriptBytes(); + + // Fetch unspents that are usable by this wallet. + const unspents = await this.getUnspentDirectives(); + + // Create an instance of our transaction builder. + const txBuilder = new TransactionBuilder(txTemplateFinal, unspents); + + // Append necessary unspents to satisfy amount requirements. + // NOTE: The order here is very important. + // We must deal with tokens first and satoshis last. + // The reason is that the change output will be determined based on transaction fee. + // TODO: Consider replacing this with just a single util function. + txBuilder + .addTokensFromUnspents() + .addTokenChangeOutputs(changeLockingBytecode) + .addSatoshisFromUnspents() + .addSatoshiChangeOutput(changeLockingBytecode); + + // Generate the transaction. + const generatedTxBytes = txBuilder.toBytes(); + + return generatedTxBytes; + } + + async sendTransaction(txTemplate: Partial) { + // Generate the transaction. + const generatedTxBytes = await this.signTransaction(txTemplate); + + // Broadcast the transaction to the blockchain. + await this.blockchain.broadcastTransaction(generatedTxBytes); + } + + private async onAddressNotification(status: string | null) { + // If wallet state has changed... + if (status !== this.status) { + // Set our new status. + this.status = status; + + // Emit an event. + this.emit('stateUpdated', this.status); + } + } + + async export() { + const allData = await this.store.all(); + const asJson = encodeExtendedJson(allData); + return asJson; + } + + async addActivity(activity: T) { + // Generate an Activity ID. + // TODO: Look more into how LibAuth intends to do this. + const activityId = Date.now().toString(); + + // Store the data. + await this.store.set(activityId, activity); + } +} + +//----------------------------------------------------------------------------- +// Factories Types/Functions +//----------------------------------------------------------------------------- + +export type WalletP2PKHFactory = ( + services: WalletServices, + genesisData?: WalletP2PKHGenesisData, +) => Promise; + +export async function useWalletP2PKH( + services: WalletServices, + genesisData?: WalletP2PKHGenesisData, +): Promise { + return (await WalletP2PKH.from(services, genesisData)) as T; +} diff --git a/packages/wallet/src/wallet/wallet-p2sh20.ts b/packages/stack/src/wallet/wallet-p2sh20.ts similarity index 100% rename from packages/wallet/src/wallet/wallet-p2sh20.ts rename to packages/stack/src/wallet/wallet-p2sh20.ts diff --git a/packages/wallet/src/wallet/wallet-p2sh32.ts b/packages/stack/src/wallet/wallet-p2sh32.ts similarity index 100% rename from packages/wallet/src/wallet/wallet-p2sh32.ts rename to packages/stack/src/wallet/wallet-p2sh32.ts diff --git a/packages/wallet/src/wallet/wallet-set.ts b/packages/stack/src/wallet/wallet-set.ts similarity index 74% rename from packages/wallet/src/wallet/wallet-set.ts rename to packages/stack/src/wallet/wallet-set.ts index bb02f8e..1222be7 100644 --- a/packages/wallet/src/wallet/wallet-set.ts +++ b/packages/stack/src/wallet/wallet-set.ts @@ -6,6 +6,7 @@ * We're going to have child wallets and we need to propagate their storage to the root WalletSet. * See remarks on Storage - it's semi-related and I'm still playing with approaches. */ +// https://github.com/bitjson/libauth/blob/d3d610de088cd98f5c391fe62dce81874eb5d95a/src/lib/engine/types/wallet-set-types.ts export class WalletSet { constructor() { console.log('TODO'); diff --git a/packages/stack/tests/utils/encryption.spec.ts b/packages/stack/tests/utils/encryption.spec.ts new file mode 100644 index 0000000..22e5961 --- /dev/null +++ b/packages/stack/tests/utils/encryption.spec.ts @@ -0,0 +1,101 @@ +import { describe, it, expect, beforeEach } from 'vitest'; + +import { Encryption } from '~/src/utils/encryption'; +import { binToUtf8, utf8ToBin } from '@bitauth/libauth'; + +describe('Encryption', () => { + let encryption: Encryption; + const testKey = 'test-secret-key-123'; + + beforeEach(async () => { + encryption = await Encryption.fromPassphrase(testKey); + }); + + describe('constructor', () => { + it('should create instance with string key', async () => { + const enc = await Encryption.fromPassphrase('test-key'); + expect(enc).toBeInstanceOf(Encryption); + }); + + it('should create instance with Uint8Array key', async () => { + const enc = await Encryption.fromBytes(utf8ToBin('test-key')); + expect(enc).toBeInstanceOf(Encryption); + }); + }); + + describe('encrypt', () => { + it('should encrypt string data', async () => { + const data = 'Hello, World!'; + const encrypted = await encryption.encrypt(data); + expect(encrypted).toBeInstanceOf(Uint8Array); + expect(encrypted.length).toBeGreaterThan(0); + }); + + it('should encrypt Uint8Array data', async () => { + const data = utf8ToBin('Hello, World!'); + const encrypted = await encryption.encrypt(data); + expect(encrypted).toBeInstanceOf(Uint8Array); + expect(encrypted.length).toBeGreaterThan(0); + }); + + it('should produce different ciphertext for same input due to random IV', async () => { + const data = 'Hello, World!'; + const encrypted1 = await encryption.encrypt(data); + const encrypted2 = await encryption.encrypt(data); + expect(Buffer.from(encrypted1).toString('hex')).not.toBe( + Buffer.from(encrypted2).toString('hex'), + ); + }); + }); + + describe('decrypt', () => { + it('should decrypt encrypted string data correctly', async () => { + const originalData = 'Hello, World!'; + const encrypted = await encryption.encrypt(originalData); + const decrypted = await encryption.decrypt(encrypted); + expect(binToUtf8(decrypted)).toBe(originalData); + }); + + it('should decrypt encrypted Uint8Array data correctly', async () => { + const originalData = utf8ToBin('Hello, World!'); + const encrypted = await encryption.encrypt(originalData); + const decrypted = await encryption.decrypt(encrypted); + expect(decrypted).toEqual(originalData); + }); + + it('should throw error when trying to decrypt string input', async () => { + await expect(encryption.decrypt('invalid-input' as any)).rejects.toThrow( + 'Encrypted payload must be Uint8Array', + ); + }); + + it('should throw error when trying to decrypt invalid data', async () => { + const invalidData = new Uint8Array([1, 2, 3, 4]); + await expect(encryption.decrypt(invalidData)).rejects.toThrow(); + }); + }); + + describe('cross-instance encryption', () => { + it('should decrypt data encrypted with different instance using same key', async () => { + const enc1 = await Encryption.fromPassphrase(testKey); + const enc2 = await Encryption.fromPassphrase(testKey); + + const originalData = 'Hello, World!'; + const encrypted = await enc1.encrypt(originalData); + const decrypted = await enc2.decrypt(encrypted); + + expect(binToUtf8(decrypted)).toBe(originalData); + }); + }); + + describe('large data handling', () => { + it('should handle large data correctly', async () => { + const largeData = 'x'.repeat(1000000); // 1MB of data + const encrypted = await encryption.encrypt(largeData); + const decrypted = await encryption.decrypt(encrypted); + expect(binToUtf8(decrypted)).toBe(largeData); + }); + }); +}); + +// diff --git a/packages/stack/tests/utils/event-emitter.spec.ts b/packages/stack/tests/utils/event-emitter.spec.ts new file mode 100644 index 0000000..f2aa778 --- /dev/null +++ b/packages/stack/tests/utils/event-emitter.spec.ts @@ -0,0 +1,251 @@ +import { describe, test, expect, vi, beforeEach } from 'vitest'; +import { EventEmitter } from '~/src/utils/event-emitter.js'; + +type TestEvents = { + test: { data: string }; + count: { value: number }; + multiPayload: { first: string; second: number }; +}; + +describe('EventEmitter', () => { + let emitter: EventEmitter; + + beforeEach(() => { + emitter = new EventEmitter(); + }); + + test('should register and trigger an event listener', () => { + const mockCallback = vi.fn(); + emitter.on('test', mockCallback); + + emitter.emit('test', { data: 'hello' }); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith({ data: 'hello' }); + }); + + test('should register multiple listeners for the same event', () => { + const mockCallback1 = vi.fn(); + const mockCallback2 = vi.fn(); + + emitter.on('test', mockCallback1); + emitter.on('test', mockCallback2); + + emitter.emit('test', { data: 'hello' }); + + expect(mockCallback1).toHaveBeenCalledTimes(1); + expect(mockCallback2).toHaveBeenCalledTimes(1); + }); + + test('should remove a specific listener', () => { + const mockCallback1 = vi.fn(); + const mockCallback2 = vi.fn(); + + emitter.on('test', mockCallback1); + emitter.on('test', mockCallback2); + + emitter.off('test', mockCallback1); + emitter.emit('test', { data: 'hello' }); + + expect(mockCallback1).not.toHaveBeenCalled(); + expect(mockCallback2).toHaveBeenCalledTimes(1); + }); + + test('should remove all listeners when removeAllListeners is called', () => { + const mockCallback1 = vi.fn(); + const mockCallback2 = vi.fn(); + + emitter.on('test', mockCallback1); + emitter.on('count', mockCallback2); + + emitter.removeAllListeners(); + + emitter.emit('test', { data: 'hello' }); + emitter.emit('count', { value: 42 }); + + expect(mockCallback1).not.toHaveBeenCalled(); + expect(mockCallback2).not.toHaveBeenCalled(); + }); + + test('should call listener only once when using once', () => { + const mockCallback = vi.fn(); + + emitter.once('test', mockCallback); + + emitter.emit('test', { data: 'first' }); + emitter.emit('test', { data: 'second' }); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith({ data: 'first' }); + }); + + test('should handle multiple event types correctly', () => { + const testCallback = vi.fn(); + const countCallback = vi.fn(); + + emitter.on('test', testCallback); + emitter.on('count', countCallback); + + emitter.emit('test', { data: 'test data' }); + emitter.emit('count', { value: 123 }); + + expect(testCallback).toHaveBeenCalledWith({ data: 'test data' }); + expect(countCallback).toHaveBeenCalledWith({ value: 123 }); + }); + + test('should debounce events correctly', async () => { + vi.useFakeTimers(); + const mockCallback = vi.fn(); + + emitter.on('test', mockCallback, 100); + + emitter.emit('test', { data: 'first' }); + emitter.emit('test', { data: 'second' }); + emitter.emit('test', { data: 'third' }); + + expect(mockCallback).not.toHaveBeenCalled(); + + await vi.runAllTimersAsync(); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith({ data: 'third' }); + + vi.useRealTimers(); + }); + + test('should handle complex payloads', () => { + const mockCallback = vi.fn(); + + emitter.on('multiPayload', mockCallback); + + const payload = { first: 'hello', second: 42 }; + emitter.emit('multiPayload', payload); + + expect(mockCallback).toHaveBeenCalledWith(payload); + }); + + test('should return true when event has listeners and false when it does not', () => { + const mockCallback = vi.fn(); + + expect(emitter.emit('test', { data: 'hello' })).toBe(false); + + emitter.on('test', mockCallback); + + expect(emitter.emit('test', { data: 'hello' })).toBe(true); + }); + + test('should handle debounced once events correctly', async () => { + vi.useFakeTimers(); + const mockCallback = vi.fn(); + + emitter.once('test', mockCallback, 100); + + emitter.emit('test', { data: 'first' }); + emitter.emit('test', { data: 'second' }); + emitter.emit('test', { data: 'third' }); + + expect(mockCallback).not.toHaveBeenCalled(); + + await vi.runAllTimersAsync(); + + expect(mockCallback).toHaveBeenCalledTimes(1); + expect(mockCallback).toHaveBeenCalledWith({ data: 'third' }); + + // Emit again after debounce + emitter.emit('test', { data: 'fourth' }); + + await vi.runAllTimersAsync(); + + expect(mockCallback).toHaveBeenCalledTimes(1); + + vi.useRealTimers(); + }); + + test('waitFor should resolve when predicate is satisfied', async () => { + const promise = emitter.waitFor( + 'test', + (payload) => payload.data === 'success', + ); + + emitter.emit('test', { data: 'not yet' }); + emitter.emit('test', { data: 'success' }); + + const result = await promise; + expect(result).toEqual({ data: 'success' }); + }); + + test('waitFor should reject on timeout', async () => { + vi.useFakeTimers(); + + const promise = emitter.waitFor( + 'test', + (payload) => payload.data === 'success', + 1000, + ); + + // Need to handle the rejection or wait for it + const promiseResult = promise.catch((error) => { + expect(error.message).toBe('Timeout waiting for event "test"'); + }); + + emitter.emit('test', { data: 'not yet' }); + + await vi.runAllTimersAsync(); + await promiseResult; + + vi.useRealTimers(); + }); + + test('waitFor should clean up listeners after resolution', async () => { + const promise = emitter.waitFor( + 'test', + (payload) => payload.data === 'success', + ); + + emitter.emit('test', { data: 'success' }); + await promise; + + // The internal listener should be removed + expect(emitter.emit('test', { data: 'another' })).toBe(false); + }); + + test('waitFor should handle multiple concurrent waits', async () => { + const promise1 = emitter.waitFor( + 'test', + (payload) => payload.data === 'first', + ); + const promise2 = emitter.waitFor( + 'test', + (payload) => payload.data === 'second', + ); + + emitter.emit('test', { data: 'second' }); + emitter.emit('test', { data: 'first' }); + + const [result1, result2] = await Promise.all([promise1, promise2]); + + expect(result1).toEqual({ data: 'first' }); + expect(result2).toEqual({ data: 'second' }); + }); + + test('waitFor should clean up timeout on successful resolution', async () => { + vi.useFakeTimers(); + + const promise = emitter.waitFor( + 'test', + (payload) => payload.data === 'success', + 1000, + ); + + // Emit success before timeout + emitter.emit('test', { data: 'success' }); + + // Fast-forward time - should not cause rejection + await vi.runAllTimersAsync(); + + const result = await promise; + expect(result).toEqual({ data: 'success' }); + + vi.useRealTimers(); + }); +}); diff --git a/packages/stack/tests/wallet/activities.bench.ts b/packages/stack/tests/wallet/activities.bench.ts new file mode 100644 index 0000000..b100aab --- /dev/null +++ b/packages/stack/tests/wallet/activities.bench.ts @@ -0,0 +1,97 @@ +import { bench, describe } from 'vitest'; +import { type Activity, Activities } from '~/src/wallet'; +import { StoreInMemory } from '~/src/storage'; // Adjust import path as needed + +// Helper function to generate test activities +function generateActivities(count: number) { + const activities: Array = []; + for (let i = 0; i < count; i++) { + activities.push({ + timestamp: BigInt(i), + type: 'WalletName', + id: `wallet${i % 1000}`, // Creates some overlap in IDs to test conflict resolution + value: `Value ${i}`, + }); + } + return activities; +} + +describe('Activities Performance Benchmarks', () => { + bench('Adding 100,000 activities sequentially', async () => { + const store = new StoreInMemory(); + const activities = new Activities(store); + const testActivities = generateActivities(100_000); + + for (const activity of testActivities) { + await activities.addActivity(activity); + } + }); + + bench('Adding 100,000 activities in batches of 1000', async () => { + const store = new StoreInMemory(); + const activities = new Activities(store); + const testActivities = generateActivities(100_000); + const batchSize = 1000; + + for (let i = 0; i < testActivities.length; i += batchSize) { + const batch = testActivities.slice(i, i + batchSize); + await Promise.all( + batch.map((activity) => activities.addActivity(activity)), + ); + } + }); + + bench('Compiling snapshot after 100,000 activities', async () => { + const store = new StoreInMemory(); + const activities = new Activities(store); + const testActivities = generateActivities(100_000); + + // First add all activities + await Promise.all( + testActivities.map((activity) => activities.addActivity(activity)), + ); + + // Benchmark the snapshot compilation + await activities.compileSnapshot(); + }); + + bench('Mixed operations benchmark', async () => { + const store = new StoreInMemory(); + const activities = new Activities(store); + + // Add activities in smaller batches with intermittent snapshot compilations + const batchSize = 10000; + const totalActivities = 100_000; + const testActivities = generateActivities(totalActivities); + + for (let i = 0; i < totalActivities; i += batchSize) { + const batch = testActivities.slice(i, i + batchSize); + await Promise.all( + batch.map((activity) => activities.addActivity(activity)), + ); + + // Compile snapshot every 100,000 activities + if ((i + batchSize) % 100000 === 0) { + await activities.compileSnapshot(); + } + } + }); + + bench('Activity conflict resolution performance', async () => { + const store = new StoreInMemory(); + const activities = new Activities(store); + + // Generate activities with lots of conflicts (same id/type) + const testActivities = Array.from({ length: 100_000 }, (_, i) => ({ + timestamp: BigInt(i), + type: 'WalletName', + id: `wallet${i % 100}`, // Only 100 unique wallets, forcing lots of conflicts + value: `Value ${i}`, + })); + + await Promise.all( + testActivities.map((activity) => activities.addActivity(activity)), + ); + await activities.compileSnapshot(); + }); +}); diff --git a/packages/stack/tests/wallet/activities.spec.ts b/packages/stack/tests/wallet/activities.spec.ts new file mode 100644 index 0000000..9cbef6c --- /dev/null +++ b/packages/stack/tests/wallet/activities.spec.ts @@ -0,0 +1,285 @@ +import { describe, test, expect, beforeEach } from 'vitest'; +import { Activities } from '~/src/wallet'; +import { StoreInMemory } from '~/src/storage'; + +describe('Activities', () => { + let store: StoreInMemory; + let activities: Activities; + + beforeEach(() => { + store = new StoreInMemory(); + activities = new Activities(store); + }); + + test('should add activity with auto-generated timestamp', async () => { + const activity = { + type: 'WalletName', + id: 'wallet1', + value: 'Test Wallet', + }; + + await activities.addActivity(activity); + await activities.compileSnapshot(); + + const storedActivity = activities.getActivity('WalletName', 'wallet1'); + expect(storedActivity).toBeDefined(); + expect(storedActivity?.timestamp).toBeDefined(); + expect(storedActivity?.value).toBe('Test Wallet'); + }); + + test('should preserve provided timestamp', async () => { + const timestamp = BigInt(1234567890000); + const activity = { + timestamp, + type: 'WalletName', + id: 'wallet1', + value: 'Test Wallet', + }; + + await activities.addActivity(activity); + const snapshot = await activities.compileSnapshot(); + + const storedActivity = activities.getActivity('WalletName', 'wallet1'); + expect(storedActivity?.timestamp).toBe(timestamp); + }); + + test('should update existing activity with newer timestamp', async () => { + const oldTimestamp = BigInt(1000); + const newTimestamp = BigInt(2000); + + await activities.addActivity({ + timestamp: oldTimestamp, + type: 'WalletName', + id: 'wallet1', + value: 'Old Name', + }); + + await activities.addActivity({ + timestamp: newTimestamp, + type: 'WalletName', + id: 'wallet1', + value: 'New Name', + }); + + await activities.compileSnapshot(); + const activity = activities.getActivity('WalletName', 'wallet1'); + + expect(activity?.value).toBe('New Name'); + expect(activity?.timestamp).toBe(newTimestamp); + }); + + test('should honor first-seen policy', async () => { + activities.registerPolicy('TransactionFirstSeen', (existing, newer) => { + return existing || newer; + }); + + const oldTimestamp = BigInt(1000); + const newTimestamp = BigInt(2000); + + await activities.addActivity({ + timestamp: oldTimestamp, + type: 'TransactionFirstSeen', + id: 'tx1', + value: 'First Value', + }); + + await activities.addActivity({ + timestamp: newTimestamp, + type: 'TransactionFirstSeen', + id: 'tx1', + value: 'Second Value', + }); + + const snapshot = await activities.compileSnapshot(); + const activity = activities.getActivity('TransactionFirstSeen', 'tx1'); + + expect(activity?.value).toBe('First Value'); + expect(activity?.timestamp).toBe(oldTimestamp); + }); + + test('should handle custom policy with comparison function', async () => { + // Policy that keeps activity with highest value + activities.registerPolicy('CustomType', (existing, newer) => { + if (!existing) return newer; + return existing.value > newer.value ? existing : newer; + }); + + await activities.addActivity({ + timestamp: BigInt(1000), + type: 'CustomType', + id: 'custom1', + value: 50, + }); + + await activities.addActivity({ + timestamp: BigInt(2000), + type: 'CustomType', + id: 'custom1', + value: 30, + }); + + const snapshot = await activities.compileSnapshot(); + const activity = activities.getActivity('CustomType', 'custom1'); + + expect(activity?.value).toBe(50); + }); + + test('should process multiple activities in chronological order', async () => { + const testActivities = [ + { + timestamp: BigInt(3000), + type: 'WalletName', + id: 'wallet1', + value: 'Name 3', + }, + { + timestamp: BigInt(1000), + type: 'WalletName', + id: 'wallet1', + value: 'Name 1', + }, + { + timestamp: BigInt(2000), + type: 'WalletName', + id: 'wallet1', + value: 'Name 2', + }, + ]; + + for (const activity of testActivities) { + await activities.addActivity(activity); + } + + await activities.compileSnapshot(); + const finalActivity = activities.getActivity('WalletName', 'wallet1'); + + expect(finalActivity?.value).toBe('Name 3'); + }); + + test('should handle multiple activity types independently', async () => { + await activities.addActivity({ + timestamp: BigInt(1000), + type: 'WalletName', + id: 'wallet1', + value: 'My Wallet', + }); + + await activities.addActivity({ + timestamp: BigInt(2000), + type: 'WalletDescription', + id: 'wallet1', + value: 'My Description', + }); + + await activities.compileSnapshot(); + + expect(activities.getActivity('WalletName', 'wallet1')?.value).toBe( + 'My Wallet', + ); + expect(activities.getActivity('WalletDescription', 'wallet1')?.value).toBe( + 'My Description', + ); + }); + + test('should update snapshot immediately when activity is added', async () => { + const activity = { + timestamp: BigInt(1000), + type: 'WalletName', + id: 'wallet1', + value: 'Test Wallet', + }; + + await activities.addActivity(activity); + + // Check snapshot without calling compileSnapshot + const storedActivity = activities.getActivity('WalletName', 'wallet1'); + expect(storedActivity?.value).toBe('Test Wallet'); + }); + + test('should handle missing timestamp gracefully', async () => { + const activity = { + type: 'WalletName', + id: 'wallet1', + value: 'Test Wallet', + }; + + await activities.addActivity(activity); + await activities.compileSnapshot(); + const storedActivity = activities.getActivity('WalletName', 'wallet1'); + + expect(storedActivity).toBeDefined(); + expect(storedActivity?.timestamp).toBeDefined(); + expect(typeof storedActivity?.timestamp).toBe('bigint'); + }); + + test('should handle store errors gracefully', async () => { + // Simulate store failure + store.set = async () => { + throw new Error('Store error'); + }; + + await expect( + activities.addActivity({ + type: 'WalletName', + id: 'wallet1', + value: 'Test', + }), + ).rejects.toThrow('Store error'); + }); + + test('should handle concurrent activity additions', async () => { + const promises: Promise[] = []; // Add explicit type annotation + for (let i = 0; i < 10; i++) { + promises.push( + activities.addActivity({ + timestamp: BigInt(i), + type: 'WalletName', + id: 'wallet1', + value: `Value ${i}`, + }), + ); + } + + await Promise.all(promises); + await activities.compileSnapshot(); + + // Should have the last value + expect(activities.getActivity('WalletName', 'wallet1')?.value).toBe( + 'Value 9', + ); + }); + + test('should cleanup old activities from store when newer versions are added', async () => { + // Add first activity + await activities.addActivity({ + timestamp: BigInt(1000), + type: 'WalletName', + id: 'wallet1', + value: 'Old Name', + }); + + expect(Object.keys(await store.all()).length).toBe(1); + + // Add newer version - should trigger cleanup of old version + await activities.addActivity({ + timestamp: BigInt(2000), + type: 'WalletName', + id: 'wallet1', + value: 'New Name', + }); + + // Should still be 1 as old activity was culled + expect(Object.keys(await store.all()).length).toBe(1); + + // Add different type + await activities.addActivity({ + timestamp: BigInt(1500), + type: 'WalletDescription', + id: 'wallet1', + value: 'Description', + }); + + // Should now have 2 activities + expect(Object.keys(await store.all()).length).toBe(2); + }); +}); diff --git a/packages/wallet/tsconfig.json b/packages/stack/tsconfig.json similarity index 60% rename from packages/wallet/tsconfig.json rename to packages/stack/tsconfig.json index 91b6b51..b35464b 100644 --- a/packages/wallet/tsconfig.json +++ b/packages/stack/tsconfig.json @@ -2,7 +2,15 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", - "rootDir": "./src" + "rootDir": "./src", + "paths": { + "~/*": [ + "./*" + ] + }, + "lib": [ + "dom" + ] }, "include": [ "src/**/*" diff --git a/packages/stack/typedoc.json b/packages/stack/typedoc.json new file mode 100644 index 0000000..93c5d86 --- /dev/null +++ b/packages/stack/typedoc.json @@ -0,0 +1,19 @@ +{ + "entryPointStrategy": "expand", + "entryPoints": ["src"], + "exclude": "**/index*.ts", + "excludeInternal": true, + "excludeProtected": true, + "excludePrivate": true, + "categorizeByGroup": false, + "indexFormat": "table", + "parametersFormat": "table", + "interfacePropertiesFormat": "table", + "classPropertiesFormat": "table", + "propertyMembersFormat": "table", + "typeDeclarationFormat": "table", + "enumMembersFormat": "table", + "useCodeBlocks": true, + "sort": ["static-first"], + "groupOrder": ["Type Aliases", "Classes", "*"] +} diff --git a/packages/stack/vite.config.ts b/packages/stack/vite.config.ts new file mode 100644 index 0000000..92df688 --- /dev/null +++ b/packages/stack/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + // vitest config + }, + resolve: { + alias: { + '~': path.resolve(__dirname, './'), + }, + }, +}); diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts deleted file mode 100644 index eb66c6f..0000000 --- a/packages/vue/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './wallet-hd-vue.js'; -export * from './wallet-p2pkh-vue.js'; diff --git a/packages/vue/src/wallet-hd-vue.ts b/packages/vue/src/wallet-hd-vue.ts deleted file mode 100644 index a56afff..0000000 --- a/packages/vue/src/wallet-hd-vue.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { HDPrivateNode } from '@xo/primitives'; -import { IBlockchain, WalletHD } from '@xo/wallet'; -import { WalletP2PKHVue, useWalletP2PKHVue } from './wallet-p2pkh-vue.js'; - -import { computed, ref, shallowRef } from 'vue'; - -export class WalletHDVue extends WalletHD { - // Reactive State. - public rShouldMonitor = ref(false); - public rWallets = shallowRef>([]); - - // Computeds. - public rBalance = computed(() => { - return this.rWallets.value.reduce( - (total, node) => total + node.rBalance.value, - 0n - ); - }); - - constructor( - node: HDPrivateNode, - electrum: IBlockchain, - walletFactory = useWalletP2PKHVue - ) { - super(node, electrum, walletFactory); - - this.events.on( - 'walletsUpdated', - (wallets) => (this.rWallets.value = wallets) - ); - - this.events.on( - 'shouldMonitorUpdated', - (shouldMonitor) => (this.rShouldMonitor.value = shouldMonitor) - ); - } -} diff --git a/packages/vue/src/wallet-p2pkh-vue.ts b/packages/vue/src/wallet-p2pkh-vue.ts deleted file mode 100644 index d7c040a..0000000 --- a/packages/vue/src/wallet-p2pkh-vue.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { PrivateKey, Transaction } from '@xo/primitives'; -import { type IBlockchain, type UTXO, WalletP2PKH } from '@xo/wallet'; - -import { computed, shallowRef } from 'vue'; -/** - * @example - * ```ts - * const walletP2PKH = WalletP2PKHVue.fromMnemonic('12 words'); - * ``` - * - * Reactive variables are prefixed with `r`. - * ```vue - *
{{ walletP2PKH.rBalance.value }}
- * ``` - */ -export class WalletP2PKHVue extends WalletP2PKH { - // Reactive State. - public rUnspents = shallowRef>([]); - public rTransactions = shallowRef>([]); - - // Computeds. - public rBalance = computed(() => { - return this.rUnspents.value.reduce( - (total, unspent) => total + unspent.valueSatoshis, - 0n - ); - }); - - constructor(privateKey: PrivateKey, electrum: IBlockchain) { - super(privateKey, electrum); - - this.events.on('unspentsUpdated', (unspents) => { - this.rUnspents.value = unspents; - }); - - this.events.on('transactionsUpdated', (transactions) => { - this.rTransactions.value = transactions; - }); - } -} - -export function useWalletP2PKHVue( - privateKey: PrivateKey, - electrum: IBlockchain -): WalletP2PKHVue { - return new WalletP2PKHVue(privateKey, electrum); -} diff --git a/packages/wallet/README.md b/packages/wallet/README.md deleted file mode 100644 index 2b0e04a..0000000 --- a/packages/wallet/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# XO Wallet - -TODO diff --git a/packages/wallet/src/blockchain/blockchain-electrum-types.ts b/packages/wallet/src/blockchain/blockchain-electrum-types.ts deleted file mode 100644 index 9b5be1a..0000000 --- a/packages/wallet/src/blockchain/blockchain-electrum-types.ts +++ /dev/null @@ -1,142 +0,0 @@ -// TODO: I think @monsterbitar (Jonathan) is working on something like this too. -// So I haven't bothered making this complete. We'll just use his once it's done. -// Or, if he's busy with other stuff, we can complete this and create an MR into Electrum-Cash. - -import type { RequestResponse } from '@electrum-cash/network'; - -// TODO: This should be imported from electrum-cash library. -// But it is not exported yet. -export type RPCParameter = string | number | boolean | null; - -export interface ElectrumRequest { - method: string; - params: Array; - response: RequestResponse; -} - -export interface AddressGetBalance extends ElectrumRequest { - method: 'blockchain.address.get_balance'; - params: [string]; - response: { - confirmed: number; - unconfirmed: number; - }; -} - -export interface AddressGetHistory extends ElectrumRequest { - method: 'blockchain.address.get_history'; - // TODO: RPCParameter accepts a null type, but this should probably be changed to undefined instead. - params: [string, number | null, number | null]; - response: Array<{ - height: number; - tx_hash: string; - fee?: number; - }>; -} - -export interface AddressGetMempool extends ElectrumRequest { - method: 'blockchain.address.get_mempool'; - params: [string]; - response: Array<{ - height: number; - tx_hash: string; - fee?: number; - }>; -} - -export interface AddressGetScriptHash extends ElectrumRequest { - method: 'blockchain.address.get_scripthash'; - params: [string]; - response: string; -} - -export interface AddressListUnspent extends ElectrumRequest { - method: 'blockchain.address.listunspent'; - params: [string, 'include_tokens' | 'exclude_tokens' | 'tokens_only']; - response: Array<{ - height: number; - token_data?: { - amount: string; - category: string; - nft?: { - capability: 'none' | 'mutable' | 'minting'; - commitment: string; - }; - }; - tx_hash: string; - tx_pos: number; - value: number; - }>; -} - -export interface AddressSubscribe extends ElectrumRequest { - method: 'blockchain.address.subscribe'; - params: [string]; - // [Address, Status] - response: [string, string | null]; -} - -export interface AddressUnsubscribe extends ElectrumRequest { - method: 'blockchain.address.unsubscribe'; - params: [string]; - response: boolean; -} - -export interface ScriptHashGetBalance extends ElectrumRequest { - method: 'blockchain.scripthash.get_balance'; - params: [string]; - response: { - confirmed: number; - unconfirmed: number; - }; -} - -export interface TransactionBroadcast extends ElectrumRequest { - method: 'blockchain.transaction.broadcast'; - params: [string]; - response: string; -} - -export interface TransactionGet extends ElectrumRequest { - method: 'blockchain.transaction.get'; - params: [string, boolean]; - response: string; -} - -export interface TransactionGetVerbose extends ElectrumRequest { - method: 'blockchain.transaction.get'; - params: [string, true]; - response: { - // TODO - }; -} - -export interface TransactionSubscribe extends ElectrumRequest { - method: 'blockchain.transaction.subscribe'; - params: [string]; - response: number | null; -} - -export interface TransactionUnsubscribe extends ElectrumRequest { - method: 'blockchain.transaction.unsubscribe'; - params: [string]; - response: boolean; -} - -export interface AddressNotification { - jsonrpc: '2.0'; - method: 'blockchain.address.subscribe'; - // [Address, Status] - params: [string, string | null]; -} - -export interface TransactionNotification { - jsonrpc: '2.0'; - method: 'blockchain.transaction.subscribe'; - // [TransactionHash, Blockheight] - params: [txHash: string, blockheight: number | null]; -} - -export type ElectrumNotification = - | AddressNotification - | TransactionNotification; diff --git a/packages/wallet/src/blockchain/blockchain-electrum.ts b/packages/wallet/src/blockchain/blockchain-electrum.ts deleted file mode 100644 index 7352942..0000000 --- a/packages/wallet/src/blockchain/blockchain-electrum.ts +++ /dev/null @@ -1,300 +0,0 @@ -import type { - IBlockchain, - AddressCallback, - TransactionCallback, - UTXO, -} from './interface.js'; -import { - ElectrumNotification, - ElectrumRequest, - AddressGetHistory, - AddressListUnspent, - TransactionGet, -} from './blockchain-electrum-types.js'; -import { EventEmitter } from '../utils/index.js'; - -import { Address, Transaction } from '@xo/primitives'; -import { ElectrumClient } from '@electrum-cash/network'; -import { hexToBin } from '@bitauth/libauth'; - -export interface BroadcastOpts { - timeout: number; - waitUntilSeen: boolean; -} - -export type BlockchainElectrumEvents = { - isConnectedUpdated: boolean; -}; - -export class BlockchainElectrum implements IBlockchain { - // Composables. - events: EventEmitter = new EventEmitter(); - electrumClient!: ElectrumClient; - - // State. - isConnected = false; - addressSubscriptions: { [address: string]: AddressCallback } = {}; - transactionSubscriptions: { [txHash: string]: TransactionCallback } = {}; - - constructor( - public readonly servers: string[], - public readonly application = 'BCHApp', - public readonly version = '1.5' - ) { - // Monitor the following properties. - this.events.monitorProperty(this, 'isConnected', 'isConnectedUpdated'); - } - - async start(): Promise { - // TODO: We want to use a Cluster instead of a Client, but as of 2022-07-07 there are issues with Subscriptions on Cluster. - // See: https://gitlab.com/GeneralProtocols/anyhedge/whitelabel/frontend/-/issues/76 - - // Iterate over each server in our list of servers. - // NOTE: We are still using an Electrum Client due to the above bug. - // But, if we cannot connect to the first server, we will try the second (and third if given, etc) as a fallback. - for (const electrumServer of this.servers) { - // Initialize an Electrum client using the given server. - const electrum = new ElectrumClient( - this.application, - this.version, - electrumServer, - 50004, - 'wss' - ); - - // Attempt to connect to this Electrum Server. - try { - await electrum.connect(); - } catch (error) { - console.warn(error); - - // If it fails to connect, try the next server in our list. - continue; - } - - // Save the client. - this.electrumClient = electrum; - - // Setup notification handler. - this.electrumClient.on('notification', this.onNotification.bind(this)); - - // Return to prevent further execution. - return; - } - - // Throw an error if we failed to connect to any of our Electrum Servers. - throw new Error( - 'Failed to connect to Electrum. Please try restarting your browser and ensure you are currently connected to the internet.' - ); - } - - async stop(): Promise { - // Disconnect from Electrum. - this.electrumClient.disconnect(); - } - - async fetchUnspents(address: string): Promise { - // Fetch unspents for the given address from our Electrum server(s). - const unspentOutputs = await this.request( - 'blockchain.address.listunspent', - address, - 'include_tokens' - ); - - // Work out the locking byecode from the address. - const lockingBytecode = - Address.fromCashAddrOrLegacy(address).toLockscriptBytes(); - - // Transform the unspents into our UTXO format. - const utxos = unspentOutputs.map((unspent) => ({ - outpointTransactionHash: hexToBin(unspent.tx_hash), - outpointIndex: unspent.tx_pos, - valueSatoshis: BigInt(unspent.value), - token: unspent.token_data - ? { - amount: BigInt(unspent.token_data.amount), - category: hexToBin(unspent.token_data.category), - nft: unspent.token_data.nft - ? { - capability: unspent.token_data.nft.capability, - commitment: hexToBin(unspent.token_data.nft.commitment), - } - : undefined, - } - : undefined, - lockingBytecode, - })); - - // Return the unspents. - return utxos; - } - - async fetchTransactions(address: string): Promise { - // Get the history of the address from our Electrum server(s). - const history = await this.request( - 'blockchain.address.get_history', - address, - 0, - -1 - ); - - // Get the transactions for each history item. - const transactions = await Promise.all( - history.map((item) => - this.request( - 'blockchain.transaction.get', - item.tx_hash, - false - ) - ) - ); - - // Decode the transactions. - const decodedTransactions = transactions.map((tx) => - Transaction.fromHex(tx) - ); - - // Return the transactions. - return decodedTransactions; - } - - // TODO: async broadcast() method - // This should accept options: { waitForSeen: boolean } - async broadcastTransaction( - tx: Uint8Array | Transaction | string, - opts: BroadcastOpts = { timeout: 10_000, waitUntilSeen: true } - ) { - // Cast the transaction. - if (!(tx instanceof Transaction)) { - tx = Transaction.fromAny(tx); - } - - // Attempt to broadcast the transaction. - await new Promise(async (resolve, reject) => { - // Set a timeout. - const timeout = setTimeout(() => { - reject(new Error(`Failed to broadcast ${tx.toHashHex()}`)); - }, opts.timeout); - - // Create a subscribe function so that we get notified when transaction is seen by network - const subscribeCallback = (_height: number | null) => { - clearTimeout(timeout); - this.unsubscribeTransaction(tx.toHashHex(), subscribeCallback); - resolve(); - }; - - // Subscribe to the transaction. - await this.subscribeTransaction(tx.toHashHex(), subscribeCallback); - - // Broadcast the transaction to our Electrum node. - await this.request('blockchain.transaction.broadcast', tx.toHex()); - }); - } - - async subscribeAddress(address: string, callback: AddressCallback) { - await this.electrumClient.subscribe( - 'blockchain.address.subscribe', - address - ); - - this.addressSubscriptions[address] = callback; - } - - async unsubscribeAddress(address: string, _callback: AddressCallback) { - if (!this.addressSubscriptions[address]) { - return; - } - - delete this.addressSubscriptions[address]; - - try { - await this.electrumClient.unsubscribe( - 'blockchain.address.unsubscribe', - address - ); - } catch (error) { - console.warn(`${error}`); - } - } - - async subscribeTransaction( - transactionHash: string, - callback: TransactionCallback - ) { - await this.electrumClient.subscribe( - 'blockchain.transaction.subscribe', - transactionHash - ); - - this.transactionSubscriptions[transactionHash] = callback; - } - - async unsubscribeTransaction( - transactionHash: string, - _callback: TransactionCallback - ) { - if (!this.transactionSubscriptions[transactionHash]) { - return; - } - - delete this.transactionSubscriptions[transactionHash]; - - try { - await this.electrumClient.unsubscribe( - 'blockchain.transaction.unsubscribe', - transactionHash - ); - } catch (error) { - console.warn(`${error}`); - } - } - - async request( - endpoint: T['method'], - ...params: T['params'] - ): Promise { - const response = await this.electrumClient.request(endpoint, ...params); - - if (response instanceof Error) { - throw response; - } - - return response; - } - - protected onNotification(data: ElectrumNotification) { - // Handle Address notification. - if (data.method === 'blockchain.address.subscribe') { - const address = data.params[0]; - const status = data.params[1]; - const subscription = this.addressSubscriptions[address]; - - if (!subscription) { - console.warn( - `Notification for address ${address} subscribed to, but has no handler` - ); - return; - } - - // Trigger the subscription's callback. - subscription(status); - } - - // Handle Transaction Notification. - if (data.method === 'blockchain.transaction.subscribe') { - const txHash = data.params[0]; - const height = data.params[1]; - const subscription = this.transactionSubscriptions[txHash]; - - if (!subscription) { - console.warn( - `Notification for tx ${txHash} subscribed to, but has no handler` - ); - return; - } - - // Trigger the subscription's callback. - subscription(height); - } - } -} diff --git a/packages/wallet/src/blockchain/interface.ts b/packages/wallet/src/blockchain/interface.ts deleted file mode 100644 index f84a3a5..0000000 --- a/packages/wallet/src/blockchain/interface.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Transaction } from '@xo/primitives'; - -import type { Output } from '@bitauth/libauth'; - -export type AddressCallback = (status: string | null) => void; -export type TransactionCallback = (height: number | null) => void; - -export interface UTXO extends Output { - outpointTransactionHash: Uint8Array; - outpointIndex: number; -} - -export interface IBlockchain { - start: () => Promise; - stop: () => Promise; - - fetchUnspents: (address: string) => Promise; - fetchTransactions: (address: string) => Promise>; - broadcastTransaction: (transaction: Uint8Array) => Promise; - - subscribeAddress: ( - address: string, - callback: AddressCallback - ) => Promise; - unsubscribeAddress: ( - address: string, - callback: AddressCallback - ) => Promise; - - subscribeTransaction: ( - transactionHash: string, - callback: TransactionCallback - ) => Promise; - unsubscribeTransaction: ( - transactionHash: string, - callback: TransactionCallback - ) => Promise; -} diff --git a/packages/wallet/src/index.browser.ts b/packages/wallet/src/index.browser.ts deleted file mode 100644 index 12aa23c..0000000 --- a/packages/wallet/src/index.browser.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Export modules that work across all platforms. -export * from './index.js'; - -// These exports will only work in a browser environment. -// NOTE: We import and export with a common alias to better support any leveraging libraries. -import { StorageIDB } from './storage/storage-idb.js'; -export { StorageIDB }; -export { StorageIDB as StorageLocal }; diff --git a/packages/wallet/src/storage/index.ts b/packages/wallet/src/storage/index.ts deleted file mode 100644 index 3579c88..0000000 --- a/packages/wallet/src/storage/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './interface.js'; -export * from './storage.js'; -export * from './storage-memory.js'; diff --git a/packages/wallet/src/storage/interface.ts b/packages/wallet/src/storage/interface.ts deleted file mode 100644 index 1a59bca..0000000 --- a/packages/wallet/src/storage/interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface IStorage { - start: () => Promise; - stop: () => Promise; - - get: (key: string) => Promise; - set: (key: string, value: T) => Promise; - delete: (key: string) => Promise; - clear: () => Promise; -} diff --git a/packages/wallet/src/storage/storage-idb.ts b/packages/wallet/src/storage/storage-idb.ts deleted file mode 100644 index 5ae7094..0000000 --- a/packages/wallet/src/storage/storage-idb.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type IStorage } from './interface'; - -import { - get as idbGet, - set as idbSet, - del as idbDel, - clear as idbClear, -} from 'idb-keyval'; - -export class StorageIDB implements IStorage { - async start() {} - - async stop() {} - - async get(key: string) { - return await idbGet(key); - } - - async set(key: string, value: T) { - await idbSet(key, value); - } - - async delete(key: string) { - idbDel(key); - } - - async clear() { - idbClear(); - } -} diff --git a/packages/wallet/src/storage/storage-memory.ts b/packages/wallet/src/storage/storage-memory.ts deleted file mode 100644 index 65c4910..0000000 --- a/packages/wallet/src/storage/storage-memory.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type IStorage } from './interface'; - -/** - * This exists for testing. It is not intended for actual use and will likely be removed. - */ -export class StorageMemory implements IStorage { - storage: { [key: string]: any } = {}; - - async start() {} - - async stop() {} - - async get(key: string) { - return this.storage[key]; - } - - async set(key: string, value: T) { - this.storage[key] = value; - } - - async delete(key: string) { - delete this.storage[key]; - } - - async clear() { - this.storage = {}; - } -} diff --git a/packages/wallet/src/storage/storage-sqlite.ts b/packages/wallet/src/storage/storage-sqlite.ts deleted file mode 100644 index cf4a45d..0000000 --- a/packages/wallet/src/storage/storage-sqlite.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { type IStorage } from './interface'; -import sqlite3 from 'sqlite3'; -import { open, Database } from 'sqlite'; - -export class StorageSQLite implements IStorage { - private db: Database | null = null; - - constructor(public filename = './storage.db') {} - - async start() { - // Open the database. - this.db = await open({ - filename: this.filename, - driver: sqlite3.Database, - }); - - // Enable WAL mode to prevent corruption. - await this.db.run('PRAGMA journal_mode = WAL;'); - - // Create the key->value store. - await this.db.exec(` - CREATE TABLE IF NOT EXISTS key_value_store ( - key TEXT PRIMARY KEY, - value JSON - ) - `); - } - - async stop() { - if (this.db) { - await this.db.close(); - this.db = null; - } - } - - async get(key: string): Promise { - if (!this.db) throw new Error('Database not initialized'); - - const row = await this.db.get( - 'SELECT value FROM key_value_store WHERE key = ?', - key - ); - return row ? JSON.parse(row.value) : undefined; - } - - async set(key: string, value: T): Promise { - if (!this.db) throw new Error('Database not initialized'); - - const jsonValue = JSON.stringify(value); - await this.db.run( - 'INSERT OR REPLACE INTO key_value_store (key, value) VALUES (?, json(?))', - key, - jsonValue - ); - } - - async delete(key: string): Promise { - if (!this.db) throw new Error('Database not initialized'); - - await this.db.run('DELETE FROM key_value_store WHERE key = ?', key); - } - - async clear(): Promise { - if (!this.db) throw new Error('Database not initialized'); - - await this.db.run('DELETE FROM key_value_store'); - } -} diff --git a/packages/wallet/src/storage/storage.ts b/packages/wallet/src/storage/storage.ts deleted file mode 100644 index 3e4581d..0000000 --- a/packages/wallet/src/storage/storage.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * The idea here is that this will implement the IStorage interface. - * But, it will allow the dev to specify multiple child IStorage interfaces for configuration. - * So, if the cloud storage is not available, the dev can specify behaviour (e.g. use quorum or just fail whatever operation was happening). - * - * @example - * ```ts - * // Actual interface will be more complex, but this is the rough idea. - * const storage = new Storage([ - * new StorageIDB(...), - * new StorageSomeCloud(...) - * ]) - * ``` - */ -export class Storage {} diff --git a/packages/wallet/src/utils/event-emitter.ts b/packages/wallet/src/utils/event-emitter.ts deleted file mode 100644 index e9ca3b7..0000000 --- a/packages/wallet/src/utils/event-emitter.ts +++ /dev/null @@ -1,61 +0,0 @@ -export class EventEmitter< - T extends Record -> extends EventTarget { - private listeners: Map> = new Map(); - - constructor() { - super(); - } - - on(type: K, listener: (detail: T[K]) => void) { - const wrappedListener = ((e: CustomEvent) => - listener(e.detail)) as EventListener; - this.addEventListener(type as string, wrappedListener); - - if (!this.listeners.has(type)) { - this.listeners.set(type, new Set()); - } - this.listeners.get(type)?.add(wrappedListener); - - return wrappedListener; - } - - off(type: K, listener: EventListener) { - this.removeEventListener(type as string, listener); - this.listeners.get(type)?.delete(listener); - } - - emit(type: K, detail: T[K]) { - const event = new CustomEvent(type as string, { detail }); - return this.dispatchEvent(event); - } - - removeAllListeners() { - this.listeners.forEach((listeners, type) => { - listeners.forEach((listener) => { - this.removeEventListener(type as string, listener); - }); - }); - this.listeners.clear(); - } - - monitorProperty( - target: ObjectType, - key: Property, - eventName: keyof T - ) { - let value = target[key]; - - Object.defineProperty(target, key, { - get: () => { - return value; - }, - set: (newValue) => { - value = newValue; - this.emit(eventName as string, newValue); - }, - enumerable: true, - configurable: true, - }); - } -} diff --git a/packages/wallet/src/utils/index.ts b/packages/wallet/src/utils/index.ts deleted file mode 100644 index 757c42f..0000000 --- a/packages/wallet/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './event-emitter.js'; diff --git a/packages/wallet/src/wallet/interface.ts b/packages/wallet/src/wallet/interface.ts deleted file mode 100644 index 210ef8c..0000000 --- a/packages/wallet/src/wallet/interface.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Transaction } from '@xo/primitives'; -import { type UTXO } from '../blockchain/index.js'; - -import { - type InputTemplate, - CompilerBCH, - CompilationData, -} from '@bitauth/libauth'; - -export type InputTemplateBCH = InputTemplate< - CompilerBCH, - false, - CompilationData ->; - -export interface IWallet { - /** - * Start monitoring this wallet for any transaction activity. - */ - start: () => Promise; - - /** - * Stop monitoring this wallet for any transaction activity. - */ - stop: () => Promise; - - /** - * Remove any event listeners from this wallet. - */ - destroy: () => Promise; - - /** - * Refresh this wallet's state. - */ - refresh: () => Promise; - - /** - * Fetch all transactions from the blockchain involving this wallet. - */ - fetchTransactions: () => Promise>; - - /** - * Fetch all unspents from the blockchain belonging to this wallet. - */ - fetchUnspents: () => Promise>; - - /** - * Fetch all unspents from the blockchain belonging to this wallet. - * - * @todo Probably remove me and just store unspents as this? - */ - fetchUnspentDirectives: () => Promise>; - - // Individual wallets should be able to send shekels too. - // send: () => Promise -} diff --git a/packages/wallet/src/wallet/wallet-base.ts b/packages/wallet/src/wallet/wallet-base.ts deleted file mode 100644 index 3888856..0000000 --- a/packages/wallet/src/wallet/wallet-base.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - type IBlockchain, - type AddressListUnspent, -} from '../blockchain/index.js'; - -import { PrivateKey, Transaction } from '@xo/primitives'; - -import { - type CompilerBCH, - type CompilationData, - type InputTemplate, -} from '@bitauth/libauth'; - -export type InputTemplateBCH = InputTemplate< - CompilerBCH, - false, - CompilationData ->; - -export abstract class WalletBase extends PrivateKey { - // Dependencies/Services - public blockchain: IBlockchain; - - constructor(privateKeyBytes: Uint8Array, blockchain: IBlockchain) { - super(privateKeyBytes); - this.blockchain = blockchain; - } - - abstract startMonitoring(): Promise; - - abstract stopMonitoring(): Promise; - - abstract getAddress(): string; - - abstract fetchHistory(): Promise>; - - abstract fetchUnspentOutputs(): Promise; - - abstract getUnspentDirectives(): Promise>; - - async refresh(): Promise { - await Promise.all([this.fetchUnspentOutputs(), this.fetchHistory()]); - } - - async onAddressNotification(status: string | null) { - // If status is null, it simply means that our subscribe call to Electrum was successful. - if (!status) { - return; - } - - // Refresh our wallet's state. - await this.refresh(); - } -} diff --git a/packages/wallet/src/wallet/wallet-hd.ts b/packages/wallet/src/wallet/wallet-hd.ts deleted file mode 100644 index b360ca1..0000000 --- a/packages/wallet/src/wallet/wallet-hd.ts +++ /dev/null @@ -1,312 +0,0 @@ -// Import the Stamp class to derive the private keys -import { type IWallet } from './interface.js'; - -import { HDPrivateNode, Transaction } from '@xo/primitives'; -import { IBlockchain } from '../blockchain/index.js'; -import { EventEmitter } from '../utils/index.js'; -import { - WalletP2PKH, - WalletP2PKHFactory, - useWalletP2PKH, -} from './wallet-p2pkh.js'; -import { - deriveSeedFromBip39Mnemonic, - encodeTransaction, - generateTransaction, - getMinimumFee, -} from '@bitauth/libauth'; - -export const DERIVATION_PATH = "m/44'/145'/0'"; -export const ADDRESS_GAP = 20; - -export type WalletHDEvents = { - shouldMonitorUpdated: boolean; - walletsUpdated: Array; -}; - -export class WalletHD - implements IWallet -{ - // Events. - public events: EventEmitter> = new EventEmitter(); - - // Mutable State. - public shouldMonitor = false; - public wallets: Array = []; - - constructor( - public node: HDPrivateNode, - public blockchain: IBlockchain, - private walletFactory: WalletP2PKHFactory = useWalletP2PKH - ) { - // Monitor the following properties and emit an event when they change. - this.events.monitorProperty(this, 'shouldMonitor', 'shouldMonitorUpdated'); - this.events.monitorProperty(this, 'wallets', 'walletsUpdated'); - } - - static fromSeed>( - this: WalletHDConstructor, - seed: Uint8Array, - blockchain: IBlockchain, - walletFactory?: WalletP2PKHFactory - ): T { - // Derive node from mnemonic. - const hdPrivateNode = HDPrivateNode.fromSeed(seed); - - // Return a new instance of this class. - return new this(hdPrivateNode, blockchain, walletFactory); - } - - static fromMnemonic>( - this: WalletHDConstructor, - mnemonic: string, - blockchain: IBlockchain, - walletFactory?: WalletP2PKHFactory - ): T { - // Derive the seed from the mnemonic. - const seed = deriveSeedFromBip39Mnemonic(mnemonic); - - // Derive node from mnemonic. - const hdPrivateNode = HDPrivateNode.fromSeed(seed); - - // Return a new instance of this class. - return new this(hdPrivateNode, blockchain, walletFactory); - } - - static fromXPriv>( - this: WalletHDConstructor, - xpriv: string, - blockchain: IBlockchain, - walletFactory?: WalletP2PKHFactory - ): T { - // Derive node from mnemonic. - const hdPrivateNode = HDPrivateNode.fromXPriv(xpriv); - - // Return a new instance of this class. - return new this(hdPrivateNode, blockchain, walletFactory); - } - - async start() { - this.shouldMonitor = true; - - await Promise.all(this.wallets.map((wallet) => wallet.start())); - } - - async stop() { - this.shouldMonitor = false; - - await Promise.all(this.wallets.map((wallet) => wallet.stop())); - } - - async destroy() { - this.events.removeAllListeners(); - } - - async fetchTransactions() { - const transactions = await Promise.all( - this.wallets.map((wallet) => wallet.fetchTransactions()) - ); - - // TODO: Remove duplicates. - - return transactions.flat(); - } - - async fetchUnspents() { - const unspents = await Promise.all( - this.wallets.map((wallet) => wallet.fetchUnspents()) - ); - - // TODO: Remove duplicates. - - return unspents.flat(); - } - - async fetchUnspentDirectives() { - const unspentDirectives = await Promise.all( - this.wallets.map((wallet) => wallet.fetchUnspentDirectives()) - ); - - // TODO: Remove duplicates. - - return unspentDirectives.flat(); - } - - async refresh() { - await Promise.all(this.wallets.map((node) => node.refresh())); - } - - async setQuantity(quantity: number) { - // Clear existing nodes. - this.wallets = this.deriveWallets(quantity); - } - - deriveWallets(count: number, startIndex = 0) { - // Create an array to store our wallets. - const wallets: Array = []; - - // Create the given count of wallets. - for (let i = 0; i < count; i++) { - const childNode = this.node.derivePath( - `${DERIVATION_PATH}/0/${startIndex + i}` - ); - const wallet = this.walletFactory( - childNode.privateKey(), - this.blockchain - ); - - if (this.shouldMonitor) { - wallet.start(); - } - - wallets.push(wallet); - } - - this.wallets = wallets; - - // Return the wallets. - return wallets; - } - - async scan(startIndex = 0, addressGap = 20) { - // Array to store active wallet nodes - const nodes: Array = []; - - let currentIndex = startIndex; - let emptyAddressCount = 0; - - // Continue scanning until we find 'addressGap' consecutive empty addresses - while (emptyAddressCount < addressGap) { - // Derive a number of wallets equivalent to our addressGap. - const batch: Array = this.deriveWallets( - addressGap, - currentIndex - ); - - // Get the transaction history of each wallet in the batch - const results = await Promise.all( - batch.map((wallet) => wallet.fetchTransactions()) - ); - - // Process the results - for (let i = 0; i < results.length; i++) { - if (results[i].length > 0) { - // This address has transactions, add it to nodes and reset empty address count - nodes.push(batch[i]); - emptyAddressCount = 0; - } else { - // This address is empty, increment the empty address count - emptyAddressCount++; - } - - currentIndex++; - - // If we've found 'addressGap' consecutive empty addresses, stop scanning - if (emptyAddressCount >= addressGap) { - break; - } - } - } - - this.wallets = nodes; - - return nodes; - } - - async sweep(payoutBytecode: Uint8Array) { - // Get a list of inputs belonging to each wallet. - const inputs = await Promise.all( - this.wallets.map((wallet) => wallet.fetchUnspentDirectives()) - ); - - // Flatten the inputs. - const inputsFlattened = inputs.flat(); - - // Calculate the total sats available in our inputs. - const totalSats = inputsFlattened.reduce((total, input) => { - if (input.unlockingBytecode instanceof Uint8Array) { - return (total += 0n); - } - - return (total += input.unlockingBytecode.valueSatoshis); - }, 0n); - - /* - // TODO: Determine the amounts and categories of any tokens in the inputs. - const tokens = inputsFlattened.reduce((tokens, input) => { - // Improve legibility by getting the token. - const token = input.unlockingBytecode.token; - - if(!token) { - return tokens; - } - - tokens[token. - - return tokens; - }, {}); - */ - // TODO: Add an output for each token. - - // We need to calculate the number of bytes so that we can calculate the fee. - // So we loop twice and store the final transaction here each time. - // 1st time will have zero fee. 2nd time will accommodate the fee. - let encodedTransaction = new Uint8Array(); - - // Create the transaction by looping twice. - // 1st loop: Transaction without a fee. - // 2nd loop: Accommodate the fee. - for (let i = 0; i < 2; i++) { - // Get the fee using 1000 sats/KB. - const feeSats = getMinimumFee(BigInt(encodedTransaction.length), 1000n); - - // Attempt to generate the transaction. - const generatedTransaction = generateTransaction({ - version: 2, - locktime: 0, - inputs: inputsFlattened, - outputs: [ - { - lockingBytecode: payoutBytecode, - valueSatoshis: totalSats - feeSats, - }, - ], - }); - - if (!generatedTransaction.success) { - console.error(generatedTransaction.errors); - - throw new Error('Failed to generate transaction'); - } - - // Encode the transaction for broadcasting (and fee estimation). - encodedTransaction = encodeTransaction(generatedTransaction.transaction); - } - - return encodedTransaction; - } - - getTransactions() { - // NOTE: We use a set so that we don't end up with duplicate transactions. - return this.wallets.reduce((txs, wallet) => { - wallet.transactions.forEach((tx) => txs.add(tx)); - return txs; // Make sure to return the accumulator - }, new Set()); - } -} - -//----------------------------------------------------------------------------- -// Factories Types/Functions -//----------------------------------------------------------------------------- - -export type WalletHDParams = ConstructorParameters; -export type WalletHDConstructor = new (...args: WalletHDParams) => T; -export type WalletHDFactory = ( - ...args: WalletHDParams -) => T; - -export function useWalletHD( - ...args: WalletHDParams -): T { - return new WalletHD(...args) as T; -} diff --git a/packages/wallet/src/wallet/wallet-p2pkh.ts b/packages/wallet/src/wallet/wallet-p2pkh.ts deleted file mode 100644 index 96eb429..0000000 --- a/packages/wallet/src/wallet/wallet-p2pkh.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { type IWallet } from './interface.js'; - -import { PrivateKey, Transaction } from '@xo/primitives'; -import { type IBlockchain, type UTXO } from '../blockchain/index.js'; -import { EventEmitter } from '../utils/index.js'; - -import { - encodeTransaction, - generateTransaction, - getMinimumFee, - walletTemplateP2pkhNonHd, - walletTemplateToCompilerBCH, -} from '@bitauth/libauth'; - -export type WalletP2PKHEvents = { - transactionsUpdated: Array; - unspentsUpdated: Array; -}; - -/** - * @example - * ```ts - * // Instantiate P2PKH Wallet. - * const walletP2PKH = WalletP2PKH.fromWIF('someWif'); - * - * // Start monitoring the balance. - * await walletP2PKH.startMonitoring(); - * ``` - */ -export class WalletP2PKH implements IWallet { - // Events. - public events: EventEmitter = new EventEmitter(); - - // State. - public transactions: Array = []; - public unspents: Array = []; - - constructor(public privateKey: PrivateKey, public blockchain: IBlockchain) { - // Monitor the following properties and emit an event when they change. - this.events.monitorProperty(this, 'transactions', 'transactionsUpdated'); - this.events.monitorProperty(this, 'unspents', 'unspentsUpdated'); - } - - static fromBytes(privateKeyBytes: Uint8Array, blockchain: IBlockchain) { - // Create a Private Key entity from the provided bytes. - const privateKey = PrivateKey.fromBytes(privateKeyBytes); - - // Return a new instance of Wallet P2PKH. - return new WalletP2PKH(privateKey, blockchain); - } - - static fromHex(privateKeyHex: string, blockchain: IBlockchain) { - // Create a Private Key entity from the provided bytes. - const privateKey = PrivateKey.fromHex(privateKeyHex); - - // Return a new instance of Wallet P2PKH. - return new WalletP2PKH(privateKey, blockchain); - } - - static fromWif(privateKeyWif: string, blockchain: IBlockchain) { - // Create a Private Key entity from the provided bytes. - const privateKey = PrivateKey.fromWIF(privateKeyWif); - - // Return a new instance of Wallet P2PKH. - return new WalletP2PKH(privateKey, blockchain); - } - - static generateRandom(blockchain: IBlockchain) { - // Create a Private Key entity from the provided bytes. - const privateKey = PrivateKey.generateRandom(); - - // Return a new instance of Wallet P2PKH. - return new WalletP2PKH(privateKey, blockchain); - } - - async start() { - await this.blockchain.subscribeAddress( - this.getAddress().toCashAddr(), - this.onAddressNotification.bind(this) - ); - } - - async stop() { - await this.blockchain.unsubscribeAddress( - this.getAddress().toCashAddr(), - this.onAddressNotification.bind(this) - ); - } - - async destroy() { - this.events.removeAllListeners(); - } - - async fetchTransactions() { - // Get this node's address. - const address = this.getAddress().toCashAddr(); - - // Get transactions involving this address. - const transactions = await this.blockchain.fetchTransactions(address); - - // Store the transactions. - this.transactions = transactions; - - // Return the fetched transactions. - return transactions; - } - - async fetchUnspents() { - // Get this wallet's address. - const address = this.getAddress().toCashAddr(); - - // Fetch unspents belonging to this address. - const unspents = await this.blockchain.fetchUnspents(address); - - // Store the unspents. - this.unspents = unspents; - - // Return the unspents. - return unspents; - } - - async fetchUnspentDirectives() { - // Get a list of unspent outputs. - const unspentOutputs = await this.fetchUnspents(); - - // Create our P2PKH Compiler. - const compilerP2PKH = walletTemplateToCompilerBCH(walletTemplateP2pkhNonHd); - - // Transform our inputs into unlocking directives. - const inputDirectives = unspentOutputs.map((unspent) => ({ - outpointIndex: unspent.outpointIndex, - outpointTransactionHash: unspent.outpointTransactionHash, - sequenceNumber: 0, - unlockingBytecode: { - compiler: compilerP2PKH, - data: { - keys: { privateKeys: { key: this.privateKey.toBytes() } }, - }, - script: 'unlock', - token: unspent.token, - valueSatoshis: unspent.valueSatoshis, - }, - })); - - // Return the directives. - return inputDirectives; - } - - async refresh(): Promise { - await Promise.all([this.fetchUnspents(), this.fetchTransactions()]); - } - - getAddress() { - return this.privateKey.derivePublicKey().deriveAddress(); - } - - async sweep(payoutBytecode: Uint8Array) { - // Flatten the inputs. - const inputs = await this.fetchUnspentDirectives(); - - // Calculate the total sats available in our inputs. - const totalSats = inputs.reduce((total, input) => { - if (input.unlockingBytecode instanceof Uint8Array) { - return (total += 0n); - } - - return (total += input.unlockingBytecode.valueSatoshis); - }, 0n); - - // We need to calculate the number of bytes so that we can calculate the fee. - // So we loop twice and store the final transaction here each time. - // 1st time will have zero fee. 2nd time will accommodate the fee. - let encodedTransaction = new Uint8Array(); - - // Create the transaction by looping twice. - // 1st loop: Transaction without a fee. - // 2nd loop: Accommodate the fee. - for (let i = 0; i < 2; i++) { - // Get the fee using 1000 sats/KB. - const feeSats = getMinimumFee(BigInt(encodedTransaction.length), 1000n); - - // Attempt to generate the transaction. - const generatedTransaction = generateTransaction({ - version: 2, - locktime: 0, - inputs: inputs, - outputs: [ - { - lockingBytecode: payoutBytecode, - valueSatoshis: totalSats - feeSats, - }, - ], - }); - - if (!generatedTransaction.success) { - console.error(generatedTransaction.errors); - - throw new Error('Failed to generate transaction'); - } - - // Encode the transaction for broadcasting (and fee estimation). - encodedTransaction = encodeTransaction(generatedTransaction.transaction); - } - - return encodedTransaction; - } - - private async onAddressNotification(status: string | null) { - // If status is null, it simply means that our subscribe call to Electrum was successful. - // TODO: I don't know if we need this anymore on later versions of Electurm. - if (!status) { - return; - } - - // Refresh our wallet's state. - await this.refresh(); - } -} - -export type WalletP2PKHFactory = ( - privateKey: PrivateKey, - blockchain: IBlockchain -) => T; - -export function useWalletP2PKH( - privateKey: PrivateKey, - blockchain: IBlockchain -): T { - return new WalletP2PKH(privateKey, blockchain) as T; -} diff --git a/tsconfig.json b/tsconfig.json index 24b697b..41d8218 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,13 +12,8 @@ "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { - "*": [ - "src/*" - ] + "*": ["src/*"] } }, - "exclude": [ - "node_modules", - "dist" - ] + "exclude": ["node_modules", "dist"] } diff --git a/typedoc.json b/typedoc.json deleted file mode 100644 index 8ce5716..0000000 --- a/typedoc.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "entryPoints": [ - "./packages/primitives/src/index.ts", - "./packages/wallet/src/index.ts", - "./packages/wallet/src/index.browser.ts", - "./packages/wallet/src/index.node.ts", - "./packages/vue/src/index.ts" - ], - "plugin": [ - "typedoc-plugin-markdown", - "typedoc-vitepress-theme" - ], - "docsRoot": "./docs", - "out": "./docs/api", - "sidebar": { - "autoConfiguration": true, - "format": "vitepress", - "pretty": true, - "collapsed": true - }, - "indexFormat": "table", - "parametersFormat": "table", - "interfacePropertiesFormat": "table", - "classPropertiesFormat": "table", - "propertyMembersFormat": "table", - "typeDeclarationFormat": "table", - "enumMembersFormat": "table", - "useCodeBlocks": true, - "sort": [ - "static-first" - ] -}