From 13ae0a8dc7dad3e7fcbcd9326a240da4f2785859 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 22 May 2026 17:13:26 +0300 Subject: [PATCH 01/16] feat: add simplicity sources and wasm plugins --- web-v2/eslint.config.js | 1 + web-v2/plugins/lwkWasmPlugin.ts | 36 +++++++++++ web-v2/plugins/simplicitySourcesPlugin.ts | 72 +++++++++++++++++++++ web-v2/simplicity-covenants.config.json | 7 ++ web-v2/src/pages/Dashboard/CovenantDemo.tsx | 14 ++++ web-v2/src/pages/Dashboard/index.tsx | 2 + web-v2/src/vite-env.d.ts | 3 + web-v2/tsconfig.node.json | 2 +- web-v2/vite.config.ts | 24 +++---- 9 files changed, 146 insertions(+), 15 deletions(-) create mode 100644 web-v2/plugins/lwkWasmPlugin.ts create mode 100644 web-v2/plugins/simplicitySourcesPlugin.ts create mode 100644 web-v2/simplicity-covenants.config.json create mode 100644 web-v2/src/pages/Dashboard/CovenantDemo.tsx diff --git a/web-v2/eslint.config.js b/web-v2/eslint.config.js index 8f5f144..29242d3 100644 --- a/web-v2/eslint.config.js +++ b/web-v2/eslint.config.js @@ -41,6 +41,7 @@ export default defineConfig([ }, }, settings: { + 'import/core-modules': ['virtual:simplicity-sources'], 'import/resolver': { typescript: {}, node: { diff --git a/web-v2/plugins/lwkWasmPlugin.ts b/web-v2/plugins/lwkWasmPlugin.ts new file mode 100644 index 0000000..340e8fc --- /dev/null +++ b/web-v2/plugins/lwkWasmPlugin.ts @@ -0,0 +1,36 @@ +import fs from 'node:fs' +import path from 'node:path' + +import type { Plugin } from 'vite' + +interface LwkWasmPluginOptions { + wasmPath: string +} + +export function lwkWasmPlugin(options: LwkWasmPluginOptions): Plugin { + return { + name: 'lwk-wasm', + + configureServer(server) { + server.middlewares.use((req, res, next) => { + if (!req.url) { + return next() + } + + if (!req.url.endsWith('lwk_wasm_bg.wasm')) { + return next() + } + + const wasmFile = path.resolve(options.wasmPath) + + if (!fs.existsSync(wasmFile)) { + return next() + } + + res.setHeader('Content-Type', 'application/wasm') + + fs.createReadStream(wasmFile).pipe(res) + }) + }, + } +} diff --git a/web-v2/plugins/simplicitySourcesPlugin.ts b/web-v2/plugins/simplicitySourcesPlugin.ts new file mode 100644 index 0000000..ec1f7a4 --- /dev/null +++ b/web-v2/plugins/simplicitySourcesPlugin.ts @@ -0,0 +1,72 @@ +import fs from 'node:fs' +import path from 'node:path' + +import type { Plugin, ResolvedConfig } from 'vite' + +// Internal Vite virtual module id. +// We dynamically generate this module from raw .simf covenant sources +// so the frontend can import them like a normal ES module. +const SIMPLICITY_SOURCES_VIRTUAL = '\0virtual:simplicity-sources' + +interface CovenantConfig { + covenants: Array<{ + id: string + path: string + }> +} + +interface SimplicitySourcesPluginOptions { + configPath: string +} + +export function simplicitySourcesPlugin(options: SimplicitySourcesPluginOptions): Plugin { + let viteConfig: ResolvedConfig + + return { + name: 'simplicity-sources', + + configResolved(config) { + viteConfig = config + }, + + resolveId(id) { + if (id === 'virtual:simplicity-sources') { + return SIMPLICITY_SOURCES_VIRTUAL + } + + return null + }, + + load(id) { + if (id !== SIMPLICITY_SOURCES_VIRTUAL) { + return null + } + + const resolvedConfigPath = path.resolve(viteConfig.root, options.configPath) + + if (!fs.existsSync(resolvedConfigPath)) { + throw new Error(`Simplicity config not found: ${resolvedConfigPath}`) + } + + const rawConfig = fs.readFileSync(resolvedConfigPath, 'utf-8') + + const config = JSON.parse(rawConfig) as CovenantConfig + + const sources: Record = {} + + for (const covenant of config.covenants) { + const covenantPath = path.resolve(viteConfig.root, covenant.path) + + if (!fs.existsSync(covenantPath)) { + throw new Error(`Simplicity covenant file not found: ${covenantPath}`) + } + + sources[covenant.id] = fs.readFileSync(covenantPath, 'utf-8').trim() + } + + return ` + export const sources = ${JSON.stringify(sources)} + ` + }, + } +} diff --git a/web-v2/simplicity-covenants.config.json b/web-v2/simplicity-covenants.config.json new file mode 100644 index 0000000..100383c --- /dev/null +++ b/web-v2/simplicity-covenants.config.json @@ -0,0 +1,7 @@ +{ + "covenants": [ + { "id": "lending", "path": "../crates/contracts/simf/lending.simf" }, + { "id": "asset_auth", "path": "../crates/contracts/simf/asset_auth.simf" }, + { "id": "script_auth", "path": "../crates/contracts/simf/script_auth.simf" } + ] +} diff --git a/web-v2/src/pages/Dashboard/CovenantDemo.tsx b/web-v2/src/pages/Dashboard/CovenantDemo.tsx new file mode 100644 index 0000000..0b8d25c --- /dev/null +++ b/web-v2/src/pages/Dashboard/CovenantDemo.tsx @@ -0,0 +1,14 @@ +import { sources } from 'virtual:simplicity-sources' + +export default function CovenantDemo() { + return ( +
+ CovenantDemo +
+
+          {JSON.stringify(sources, null, 2)}
+        
+
+
+ ) +} diff --git a/web-v2/src/pages/Dashboard/index.tsx b/web-v2/src/pages/Dashboard/index.tsx index 0d4bc02..322f0ee 100644 --- a/web-v2/src/pages/Dashboard/index.tsx +++ b/web-v2/src/pages/Dashboard/index.tsx @@ -1,3 +1,4 @@ +import CovenantDemo from './CovenantDemo' import { WalletDemo } from './WalletDemo' export default function DashboardPage() { @@ -5,6 +6,7 @@ export default function DashboardPage() {

Dashboard

+
) } diff --git a/web-v2/src/vite-env.d.ts b/web-v2/src/vite-env.d.ts index 9a88a97..e95b5f3 100644 --- a/web-v2/src/vite-env.d.ts +++ b/web-v2/src/vite-env.d.ts @@ -9,3 +9,6 @@ interface ImportMetaEnv { interface ImportMeta { readonly env: ImportMetaEnv } +declare module 'virtual:simplicity-sources' { + export const sources: Record +} diff --git a/web-v2/tsconfig.node.json b/web-v2/tsconfig.node.json index 7198bdd..9ceff2a 100644 --- a/web-v2/tsconfig.node.json +++ b/web-v2/tsconfig.node.json @@ -19,5 +19,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["vite.config.ts", "scripts/**/*.ts"] + "include": ["vite.config.ts", "scripts/**/*.ts", "plugins/**/*.ts"] } diff --git a/web-v2/vite.config.ts b/web-v2/vite.config.ts index 3c2c642..9516796 100644 --- a/web-v2/vite.config.ts +++ b/web-v2/vite.config.ts @@ -2,10 +2,12 @@ import path from 'node:path' import { fileURLToPath } from 'node:url' import react from '@vitejs/plugin-react' -import fs from 'fs' import { defineConfig } from 'vite' import { checker } from 'vite-plugin-checker' +import { lwkWasmPlugin } from './plugins/lwkWasmPlugin' +import { simplicitySourcesPlugin } from './plugins/simplicitySourcesPlugin' + const root = path.dirname(fileURLToPath(import.meta.url)) // https://vite.dev/config/ @@ -16,19 +18,13 @@ export default defineConfig({ }, }, plugins: [ - { - name: 'lwk-wasm-dev', - configureServer(server) { - const wasmFile = path.resolve(root, '..', 'lwk_wasm', 'pkg_web', 'lwk_wasm_bg.wasm') - server.middlewares.use((req, res, next) => { - if (!req.url) return next() - if (!req.url.endsWith('lwk_wasm_bg.wasm')) return next() - if (!fs.existsSync(wasmFile)) return next() - res.setHeader('Content-Type', 'application/wasm') - fs.createReadStream(wasmFile).pipe(res) - }) - }, - }, + simplicitySourcesPlugin({ + configPath: './simplicity-covenants.config.json', + }), + + lwkWasmPlugin({ + wasmPath: '../lwk_wasm/pkg_web/lwk_wasm_bg.wasm', + }), react(), checker({ overlay: { From 7ed13f2c59dbf917214692f46bb47bd401ce2599 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Sat, 23 May 2026 00:55:03 +0300 Subject: [PATCH 02/16] feat: integrate XOnlyPublicKey and scriptAuth functionality --- web-v2/src/lib/wallet-core/connector/jade.ts | 20 +++--- web-v2/src/lib/wallet-core/connector/seed.ts | 21 ++++--- web-v2/src/lib/wallet-core/connector/types.ts | 4 +- web-v2/src/lwk/index.ts | 34 +++------- web-v2/src/pages/Dashboard/CovenantDemo.tsx | 63 +++++++++++++++++-- web-v2/src/pages/Dashboard/WalletDemo.tsx | 5 +- web-v2/src/providers/lwk/LwkProvider.tsx | 22 ++++--- web-v2/src/providers/lwk/types.ts | 2 - .../src/providers/wallet/WalletProvider.tsx | 41 ++++++------ web-v2/src/providers/wallet/types.ts | 4 +- web-v2/src/simplicity/covenants/scriptAuth.ts | 36 +++++++++++ web-v2/src/utils/hex.ts | 11 ++++ web-v2/src/vite-env.d.ts | 8 ++- 13 files changed, 186 insertions(+), 85 deletions(-) create mode 100644 web-v2/src/simplicity/covenants/scriptAuth.ts diff --git a/web-v2/src/lib/wallet-core/connector/jade.ts b/web-v2/src/lib/wallet-core/connector/jade.ts index 66cbdf0..e589cc5 100644 --- a/web-v2/src/lib/wallet-core/connector/jade.ts +++ b/web-v2/src/lib/wallet-core/connector/jade.ts @@ -1,6 +1,11 @@ -import type { Jade, Network, Pset, Wollet, WolletDescriptor } from 'lwk_web' - -import type { Lwk } from '@/lwk' +import { + Jade, + type Network, + type Pset, + Singlesig, + type Wollet, + type WolletDescriptor, +} from 'lwk_web' import type { ConnectionStatus, JadeVersionInfo, WalletType } from '../types' import type { WalletConnector } from './types' @@ -17,17 +22,14 @@ export class JadeConnector implements WalletConnector { private busy = false private _id: string | null = null - constructor( - private readonly lwk: Lwk, - private readonly lwkNetwork: Network, - ) {} + constructor(private readonly lwkNetwork: Network) {} async connect(): Promise { if (this.jade !== null) return // HACK: The TS bindings declare this as a sync constructor, but wasm-bindgen // generates an async constructor under the hood that returns a Promise. // `await new this.lwk.Jade(...)` is intentional — not a mistake. - this.jade = await new this.lwk.Jade(this.lwkNetwork, true) + this.jade = await new Jade(this.lwkNetwork, true) } disconnect(): void { @@ -91,7 +93,7 @@ export class JadeConnector implements WalletConnector { const addrResult = wollet.address() const index = addrResult.index() const path = wollet.addressFullPath(index) - const singlesig = this.lwk.Singlesig.from(variant) + const singlesig = Singlesig.from(variant) return await this.jade.getReceiveAddressSingle(singlesig, path) } diff --git a/web-v2/src/lib/wallet-core/connector/seed.ts b/web-v2/src/lib/wallet-core/connector/seed.ts index 62f4e15..1c5222d 100644 --- a/web-v2/src/lib/wallet-core/connector/seed.ts +++ b/web-v2/src/lib/wallet-core/connector/seed.ts @@ -1,6 +1,12 @@ -import type { Network, Pset, Signer, WolletDescriptor } from 'lwk_web' - -import type { Lwk } from '@/lwk' +import { + Mnemonic, + type Network, + type Pset, + Signer, + simplicityDeriveXonlyPubkey, + type WolletDescriptor, + XOnlyPublicKey, +} from 'lwk_web' import type { ConnectionStatus, WalletType } from '../types' import type { WalletConnector } from './types' @@ -19,7 +25,6 @@ export class SeedConnector implements WalletConnector { private _id: string | null = null constructor( - private readonly lwk: Lwk, private readonly lwkNetwork: Network, private readonly mnemonicStr: string, ) { @@ -28,8 +33,8 @@ export class SeedConnector implements WalletConnector { async connect(): Promise { if (this.signer !== null) return - const mnemonic = new this.lwk.Mnemonic(this.mnemonicStr) - this.signer = new this.lwk.Signer(mnemonic, this.lwkNetwork) + const mnemonic = new Mnemonic(this.mnemonicStr) + this.signer = new Signer(mnemonic, this.lwkNetwork) this._id = crypto.randomUUID() } @@ -61,11 +66,11 @@ export class SeedConnector implements WalletConnector { return this.signer.sign(pset) } - async getXOnlyPublicKey(): Promise { + async getXOnlyPublicKey(): Promise { if (!this.signer) throw new Error('SeedConnector: not connected') //github.com/BlockstreamResearch/smplx/blob/1945d11b47fff8838c3e99c210133519a9522324/crates/sdk/src/signer/core.rs#L621C1-L628C2 const path = this.lwkNetwork.isMainnet() ? 'm/84h/1776h/0h/0/1' : 'm/84h/1h/0h/0/1' - return this.lwk.simplicityDeriveXonlyPubkey(this.signer, path).toString() + return simplicityDeriveXonlyPubkey(this.signer, path) } async getConnectionStatus(): Promise { diff --git a/web-v2/src/lib/wallet-core/connector/types.ts b/web-v2/src/lib/wallet-core/connector/types.ts index f6c0bfb..2a422e8 100644 --- a/web-v2/src/lib/wallet-core/connector/types.ts +++ b/web-v2/src/lib/wallet-core/connector/types.ts @@ -1,4 +1,4 @@ -import type { Pset, Wollet, WolletDescriptor } from 'lwk_web' +import type { Pset, Wollet, WolletDescriptor, XOnlyPublicKey } from 'lwk_web' import type { ConnectionStatus, WalletType } from '../types' @@ -10,6 +10,6 @@ export interface WalletConnector { signPset(pset: Pset): Promise isConnected: boolean getConnectionStatus(): Promise - getXOnlyPublicKey?(): Promise + getXOnlyPublicKey?(): Promise getVerifiedReceiveAddress?(variant: WalletType, wollet: Wollet): Promise } diff --git a/web-v2/src/lwk/index.ts b/web-v2/src/lwk/index.ts index edb4f57..ec72f57 100644 --- a/web-v2/src/lwk/index.ts +++ b/web-v2/src/lwk/index.ts @@ -1,10 +1,4 @@ -import type { - EsploraClient, - Network, - SimplicityArguments, - Transaction, - XOnlyPublicKey, -} from 'lwk_web' +import { EsploraClient, Network, type Transaction } from 'lwk_web' import { env, type NetworkName } from '@/constants/env' @@ -20,14 +14,14 @@ export async function getLwk(): Promise { return lwk } -export function createLwkNetwork(network: NetworkName, lwk: Lwk): Network { +export function createLwkNetwork(network: NetworkName): Network { switch (network) { case 'liquid': - return lwk.Network.mainnet() + return Network.mainnet() case 'liquidtestnet': - return lwk.Network.testnet() + return Network.testnet() case 'regtest': - return lwk.Network.regtestDefault() + return Network.regtestDefault() } } @@ -35,26 +29,12 @@ export interface PsetWithExtractTx { extractTx(): Transaction } -export interface CreateP2trAddressParams { - source: string - args: SimplicityArguments - internalKey: XOnlyPublicKey - network: NetworkName -} - -export function createP2trAddress(lwk: Lwk, params: CreateP2trAddressParams): string { - const program = lwk.SimplicityProgram.load(params.source, params.args) - const net = createLwkNetwork(params.network, lwk) - const address = program.createP2trAddress(params.internalKey, net) - return address.toString() -} - /** * Creates an EsploraClient configured for waterfalls + utxoOnly scanning. * Waterfalls provides fast indexed encrypted UTXO discovery vs slow sequential HD scan. */ -export function createEsploraClient(lwk: Lwk, lwkNetwork: Network): EsploraClient { - const client = new lwk.EsploraClient( +export function createEsploraClient(lwkNetwork: Network): EsploraClient { + const client = new EsploraClient( lwkNetwork, `${env.VITE_WATERFALLS_URL}/api`, true, // waterfalls diff --git a/web-v2/src/pages/Dashboard/CovenantDemo.tsx b/web-v2/src/pages/Dashboard/CovenantDemo.tsx index 0b8d25c..acfa3f3 100644 --- a/web-v2/src/pages/Dashboard/CovenantDemo.tsx +++ b/web-v2/src/pages/Dashboard/CovenantDemo.tsx @@ -1,12 +1,67 @@ +import type { XOnlyPublicKey } from 'lwk_web' +import { useEffect, useMemo, useState } from 'react' import { sources } from 'virtual:simplicity-sources' +import { useLwk } from '@/providers/lwk/useLwk' +import { useWallet } from '@/providers/wallet/useWallet' +import { loadScriptAuthProgram } from '@/simplicity/covenants/scriptAuth' + export default function CovenantDemo() { + const { lwkNetwork } = useLwk() + const { connectionStatus, getXOnlyPublicKey } = useWallet() + + const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) + + const [error, setError] = useState(null) + + useEffect(() => { + if (connectionStatus !== 'ready') return + let cancelled = false + getXOnlyPublicKey() + .then(key => { + if (!cancelled && key) setXOnlyPublicKey(key) + }) + .catch(err => { + if (!cancelled) setError(String(err)) + }) + return () => { + cancelled = true + } + }, [connectionStatus, getXOnlyPublicKey]) + + const result = useMemo(() => { + if (!xOnlyPublicKey) { + return null + } + + const scriptHash = crypto.getRandomValues(new Uint8Array(32)) + const program = loadScriptAuthProgram(scriptHash) + const address = program.createP2trAddress(xOnlyPublicKey, lwkNetwork).toString() + + return { + sourceLoaded: !!sources.script_auth, + xOnlyPublicKey: xOnlyPublicKey.toString(), + scriptHash: Array.from(scriptHash), + address, + cmr: program.cmr.toString(), + } + }, [lwkNetwork, xOnlyPublicKey]) + return ( -
- CovenantDemo -
+
+
+
ScriptAuth Covenant Smoke Test
+
-          {JSON.stringify(sources, null, 2)}
+          {JSON.stringify(
+            {
+              hasPubkey: !!xOnlyPublicKey,
+              error,
+              result,
+            },
+            null,
+            2,
+          )}
         
diff --git a/web-v2/src/pages/Dashboard/WalletDemo.tsx b/web-v2/src/pages/Dashboard/WalletDemo.tsx index 238537c..2201a53 100644 --- a/web-v2/src/pages/Dashboard/WalletDemo.tsx +++ b/web-v2/src/pages/Dashboard/WalletDemo.tsx @@ -1,3 +1,4 @@ +import type { XOnlyPublicKey } from 'lwk_web' import { useEffect, useState } from 'react' import { fetchTxConfirmations } from '@/api/esplora/methods' @@ -45,7 +46,7 @@ export function WalletDemo() { const [sending, setSending] = useState(false) const [txConfirmations, setTxConfirmations] = useState(null) const [verifyingAddress, setVerifyingAddress] = useState(false) - const [xOnlyPubKey, setXOnlyPubKey] = useState(null) + const [xOnlyPubKey, setXOnlyPubKey] = useState(null) const [lastReceiveAddress, setLastReceiveAddress] = useState(null) useEffect(() => { @@ -235,7 +236,7 @@ export function WalletDemo() { {env.VITE_DEBUG_MNEMONIC && xOnlyPubKey && (

X-Only Public Key (Simplicity)

- {xOnlyPubKey} + {xOnlyPubKey.toString()}
)} diff --git a/web-v2/src/providers/lwk/LwkProvider.tsx b/web-v2/src/providers/lwk/LwkProvider.tsx index 31b2457..0a03a6b 100644 --- a/web-v2/src/providers/lwk/LwkProvider.tsx +++ b/web-v2/src/providers/lwk/LwkProvider.tsx @@ -1,19 +1,22 @@ import { useEffect, useMemo, useState } from 'react' import { env } from '@/constants/env' -import { createLwkNetwork, getLwk, type Lwk } from '@/lwk' +import { createLwkNetwork, getLwk } from '@/lwk' import { LwkContext } from './LwkContext' const network = env.VITE_NETWORK export function LwkProvider({ children }: { children: React.ReactNode }) { - const [lwk, setLwk] = useState(null) + const [isReady, setIsReady] = useState(false) + useEffect(() => { let cancelled = false - getLwk().then(instance => { - if (!cancelled) setLwk(instance) + getLwk().then(() => { + if (!cancelled) { + setIsReady(true) + } }) return () => { @@ -21,7 +24,13 @@ export function LwkProvider({ children }: { children: React.ReactNode }) { } }, []) - const lwkNetwork = useMemo(() => (lwk ? createLwkNetwork(network, lwk) : null), [lwk]) + const lwkNetwork = useMemo(() => { + if (!isReady) { + return null + } + + return createLwkNetwork(network) + }, [isReady]) useEffect(() => { return () => { @@ -29,7 +38,7 @@ export function LwkProvider({ children }: { children: React.ReactNode }) { } }, [lwkNetwork]) - if (!lwk || !lwkNetwork) { + if (!lwkNetwork) { // TODO: Replace with proper loader after UI framework setup return
Loading...
} @@ -37,7 +46,6 @@ export function LwkProvider({ children }: { children: React.ReactNode }) { return ( (null) const connectingRef = useRef(false) @@ -150,24 +150,25 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { const walletType: WalletType = env.VITE_DEBUG_MNEMONIC ? 'Wpkh' : variant connector = env.VITE_DEBUG_MNEMONIC - ? new SeedConnector(lwk, lwkNetwork, env.VITE_DEBUG_MNEMONIC) - : new JadeConnector(lwk, lwkNetwork) + ? new SeedConnector(lwkNetwork, env.VITE_DEBUG_MNEMONIC) + : new JadeConnector(lwkNetwork) await connector.connect() - const connectionStatus = await connector.getConnectionStatus() - // Show the intermediate state (locked/ready) before PIN prompt blocks. - setState(s => ({ - ...s, - connectionStatus, - connectorId: connector!.id, - walletType, - })) + // Show 'locked' in the UI before getDescriptor() blocks waiting for Jade PIN. + if (connectionStatus === 'locked') { + setState(s => ({ + ...s, + connectionStatus: 'locked', + connectorId: connector!.id, + walletType, + })) + } const descriptor = await connector.getDescriptor(walletType) - const wollet = new lwk.Wollet(lwkNetwork, descriptor) - const esploraClient = createEsploraClient(lwk, lwkNetwork) + const wollet = new Wollet(lwkNetwork, descriptor) + const esploraClient = createEsploraClient(lwkNetwork) sessionRef.current = { connector, descriptor, wollet, esploraClient } @@ -208,7 +209,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { connectingRef.current = false } }, - [lwk, lwkNetwork, setSavedSession], + [lwkNetwork, setSavedSession], ) const resumeSession = useCallback(async () => { @@ -263,7 +264,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { return session.wollet.address().address().toString() }, []) - const getXOnlyPublicKey = useCallback(async (): Promise => { + const getXOnlyPublicKey = useCallback(async (): Promise => { const session = sessionRef.current if (!session) return null return session.connector.getXOnlyPublicKey?.() ?? null @@ -283,14 +284,12 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { const session = sessionRef.current if (!session) throw new Error('WalletProvider: not connected') - const addr = lwk.Address.parse(recipientAddress, lwkNetwork) - const txBuilder = await new lwk.TxBuilder(lwkNetwork) - .feeRate(100) - .addLbtcRecipient(addr, satoshi) + const addr = Address.parse(recipientAddress, lwkNetwork) + const txBuilder = await new TxBuilder(lwkNetwork).feeRate(100).addLbtcRecipient(addr, satoshi) const pset = txBuilder.finish(session.wollet) return signAndBroadcast(pset) }, - [lwk, lwkNetwork, signAndBroadcast], + [lwkNetwork, signAndBroadcast], ) return ( diff --git a/web-v2/src/providers/wallet/types.ts b/web-v2/src/providers/wallet/types.ts index ca626c6..a471ac8 100644 --- a/web-v2/src/providers/wallet/types.ts +++ b/web-v2/src/providers/wallet/types.ts @@ -1,4 +1,4 @@ -import type { EsploraClient, Pset, Wollet, WolletDescriptor } from 'lwk_web' +import type { EsploraClient, Pset, Wollet, WolletDescriptor, XOnlyPublicKey } from 'lwk_web' import type { WalletConnector } from '@/lib/wallet-core/connector/types' import type { ConnectionStatus, WalletType } from '@/lib/wallet-core/types' @@ -11,7 +11,7 @@ export interface WalletContextValue extends WalletState { sendLbtc(recipientAddress: string, satoshi: bigint): Promise getLastReceiveAddress(): Promise verifyReceiveAddress(): Promise - getXOnlyPublicKey(): Promise + getXOnlyPublicKey(): Promise resumeSession(): Promise savedSession: SavedSession | null } diff --git a/web-v2/src/simplicity/covenants/scriptAuth.ts b/web-v2/src/simplicity/covenants/scriptAuth.ts new file mode 100644 index 0000000..630229d --- /dev/null +++ b/web-v2/src/simplicity/covenants/scriptAuth.ts @@ -0,0 +1,36 @@ +import { + SimplicityArguments, + SimplicityProgram, + SimplicityTypedValue, + SimplicityWitnessValues, +} from 'lwk_web' +import { sources } from 'virtual:simplicity-sources' + +import { bytes32ToHex } from '@/utils/hex' + +export function loadScriptAuthProgram(scriptHash: Uint8Array): SimplicityProgram { + return SimplicityProgram.load(sources.script_auth, buildScriptAuthArguments(scriptHash)) +} + +// TODO: Generate typed argument/witness names from Simplicity source code +const ARGUMENTS = { + SCRIPT_HASH: 'SCRIPT_HASH', +} as const + +const WITNESS = { + INPUT_SCRIPT_INDEX: 'INPUT_SCRIPT_INDEX', +} as const + +export function buildScriptAuthArguments(scriptHash: Uint8Array): SimplicityArguments { + return new SimplicityArguments().addValue( + ARGUMENTS.SCRIPT_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(scriptHash)), + ) +} + +export function buildScriptAuthWitness(inputScriptIndex: number): SimplicityWitnessValues { + return new SimplicityWitnessValues().addValue( + WITNESS.INPUT_SCRIPT_INDEX, + SimplicityTypedValue.fromU32(inputScriptIndex), + ) +} diff --git a/web-v2/src/utils/hex.ts b/web-v2/src/utils/hex.ts index 5109623..cff9cb2 100644 --- a/web-v2/src/utils/hex.ts +++ b/web-v2/src/utils/hex.ts @@ -27,3 +27,14 @@ export function bytesToHex(bytes: Uint8Array): string { .map(byte => byte.toString(16).padStart(2, '0')) .join('') } + +export function bytes32ToHex(b: Uint8Array): string { + if (b.length !== 32) throw new Error('Expected 32 bytes') + return bytesToHex(b) +} + +export function hexToBytes32(hex: string): Uint8Array { + const s = normalizeHex(hex) + if (s.length !== 64) throw new Error('Expected 64 hex chars for 32-byte value') + return hexToBytes(s) +} diff --git a/web-v2/src/vite-env.d.ts b/web-v2/src/vite-env.d.ts index e95b5f3..0a75125 100644 --- a/web-v2/src/vite-env.d.ts +++ b/web-v2/src/vite-env.d.ts @@ -10,5 +10,11 @@ interface ImportMeta { readonly env: ImportMetaEnv } declare module 'virtual:simplicity-sources' { - export const sources: Record + export interface SimplicitySources { + lending: string + asset_auth: string + script_auth: string + } + + export const sources: SimplicitySources } From 2f14c6044689a13ab8ed48fe42f77cf305388e37 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Sat, 30 May 2026 12:45:23 +0300 Subject: [PATCH 03/16] refactor(web-v2): move PSET builders out of WalletProvider --- .env.example | 2 +- .../buildExplicitRecipientPset.ts | 27 ++ .../buildExternalExplicitRecipientPset.ts | 41 ++ web-v2/src/lib/pset-builder/index.ts | 2 + web-v2/src/pages/Dashboard/CovenantDemo.tsx | 358 ++++++++++++++++-- web-v2/src/pages/Dashboard/WalletDemo.tsx | 12 +- .../src/providers/wallet/WalletProvider.tsx | 35 +- web-v2/src/providers/wallet/types.ts | 13 +- 8 files changed, 449 insertions(+), 41 deletions(-) create mode 100644 web-v2/src/lib/pset-builder/buildExplicitRecipientPset.ts create mode 100644 web-v2/src/lib/pset-builder/buildExternalExplicitRecipientPset.ts create mode 100644 web-v2/src/lib/pset-builder/index.ts diff --git a/.env.example b/.env.example index 063fbf0..eb5fcb8 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,6 @@ WEB_PORT=80 # Build-time variables for web image VITE_API_URL=http://localhost/api -VITE_ESPLORA_BASE_URL=https://blockstream.info/liquidtestnet/api +VITE_ESPLORA_BASE_URL=https://blockstream.info/liquidtestnet VITE_ESPLORA_EXPLORER_URL=https://blockstream.info/liquidtestnet VITE_FAUCET_URL=https://liquidtestnet.com/faucet diff --git a/web-v2/src/lib/pset-builder/buildExplicitRecipientPset.ts b/web-v2/src/lib/pset-builder/buildExplicitRecipientPset.ts new file mode 100644 index 0000000..233f26c --- /dev/null +++ b/web-v2/src/lib/pset-builder/buildExplicitRecipientPset.ts @@ -0,0 +1,27 @@ +import { Address, type Network, type OutPoint, type Pset, TxBuilder, type Wollet } from 'lwk_web' + +interface BuildExplicitRecipientPsetParams { + wollet: Wollet + network: Network + recipientAddress: string + outpoints: OutPoint[] + satoshi: bigint + feeRate?: number +} + +export function buildExplicitRecipientPset({ + wollet, + network, + recipientAddress, + outpoints, + satoshi, + feeRate = 1, +}: BuildExplicitRecipientPsetParams): Pset { + const address = new Address(recipientAddress) + + return new TxBuilder(network) + .feeRate(feeRate) + .setWalletUtxos(outpoints) + .addExplicitRecipient(address, satoshi, network.policyAsset()) + .finish(wollet) +} diff --git a/web-v2/src/lib/pset-builder/buildExternalExplicitRecipientPset.ts b/web-v2/src/lib/pset-builder/buildExternalExplicitRecipientPset.ts new file mode 100644 index 0000000..8ce94c4 --- /dev/null +++ b/web-v2/src/lib/pset-builder/buildExternalExplicitRecipientPset.ts @@ -0,0 +1,41 @@ +import { + Address, + type ExternalUtxo, + type Network, + type OutPoint, + type Pset, + TxBuilder, + type Wollet, +} from 'lwk_web' + +interface BuildExternalExplicitRecipientPsetParams { + wollet: Wollet + network: Network + recipientAddress: string + outpoints: OutPoint[] + externalUtxos: ExternalUtxo[] + satoshi: bigint + feeRate?: number +} + +export function buildExternalExplicitRecipientPset({ + wollet, + network, + recipientAddress, + outpoints, + externalUtxos, + satoshi, + feeRate = 1, +}: BuildExternalExplicitRecipientPsetParams): Pset { + const address = new Address(recipientAddress) + void externalUtxos + + return ( + new TxBuilder(network) + .feeRate(feeRate) + .setWalletUtxos(outpoints) + // .addExternalUtxos(externalUtxos) // TODO: expose TxBuilder.addExternalUtxos() in lwk_web. + .addExplicitRecipient(address, satoshi, network.policyAsset()) + .finish(wollet) + ) +} diff --git a/web-v2/src/lib/pset-builder/index.ts b/web-v2/src/lib/pset-builder/index.ts new file mode 100644 index 0000000..96e432d --- /dev/null +++ b/web-v2/src/lib/pset-builder/index.ts @@ -0,0 +1,2 @@ +export { buildExplicitRecipientPset } from './buildExplicitRecipientPset' +export { buildExternalExplicitRecipientPset } from './buildExternalExplicitRecipientPset' diff --git a/web-v2/src/pages/Dashboard/CovenantDemo.tsx b/web-v2/src/pages/Dashboard/CovenantDemo.tsx index acfa3f3..bf9bdb0 100644 --- a/web-v2/src/pages/Dashboard/CovenantDemo.tsx +++ b/web-v2/src/pages/Dashboard/CovenantDemo.tsx @@ -1,68 +1,374 @@ -import type { XOnlyPublicKey } from 'lwk_web' -import { useEffect, useMemo, useState } from 'react' +import { + Address, + ExternalUtxo, + type Pset, + Transaction, + TxOutSecrets, + type WalletTxOut, + type XOnlyPublicKey, +} from 'lwk_web' +import { useEffect, useState } from 'react' import { sources } from 'virtual:simplicity-sources' +import { fetchAddressUtxo, fetchTxConfirmations, fetchTxRaw } from '@/api/esplora/methods' +import { getTxExplorerUrl } from '@/api/esplora/utils' +import { buildExplicitRecipientPset, buildExternalExplicitRecipientPset } from '@/lib/pset-builder' import { useLwk } from '@/providers/lwk/useLwk' import { useWallet } from '@/providers/wallet/useWallet' import { loadScriptAuthProgram } from '@/simplicity/covenants/scriptAuth' +interface CovenantResult { + sourceLoaded: boolean + xOnlyPublicKey: string + receiveAddress: string + scriptHash: number[] + address: string + cmr: string + txOutputs: TxOutputSummary[] + utxos: { + covenant: unknown[] + wallet: WalletUtxoSummary[] + } +} + +interface WalletUtxoSummary { + outpoint: string + address: string + asset: string + value: string + wildcardIndex: number +} + +interface TxOutputSummary { + asset?: string + amount?: string + script: string +} + +function summarizeWalletUtxo(utxo: WalletTxOut): WalletUtxoSummary { + const outpoint = utxo.outpoint() + const unblinded = utxo.unblinded() + + return { + outpoint: `${outpoint.txid().toString()}:${outpoint.vout()}`, + address: utxo.address().toString(), + asset: unblinded.asset().toString(), + value: unblinded.value().toString(), + wildcardIndex: utxo.wildcardIndex(), + } +} + export default function CovenantDemo() { const { lwkNetwork } = useLwk() - const { connectionStatus, getXOnlyPublicKey } = useWallet() + + const { + connectionStatus, + getXOnlyPublicKey, + getReceiveAddress, + getWalletUtxos, + getWollet, + signAndBroadcast, + } = useWallet() const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) + const [result, setResult] = useState(null) + const [error, setError] = useState(null) + const [pset, setPset] = useState(null) + const [psetString, setPsetString] = useState(null) + const [broadcasting, setBroadcasting] = useState(false) + const [broadcastTxid, setBroadcastTxid] = useState(null) + const [broadcastError, setBroadcastError] = useState(null) + const [txConfirmations, setTxConfirmations] = useState(null) useEffect(() => { - if (connectionStatus !== 'ready') return + if (connectionStatus !== 'ready') { + return + } + let cancelled = false - getXOnlyPublicKey() - .then(key => { - if (!cancelled && key) setXOnlyPublicKey(key) - }) - .catch(err => { - if (!cancelled) setError(String(err)) - }) + + async function run(): Promise { + try { + setError(null) + setXOnlyPublicKey(null) + setResult(null) + setPset(null) + setPsetString(null) + setBroadcastTxid(null) + setBroadcastError(null) + setTxConfirmations(null) + + const key = await getXOnlyPublicKey() + + if (!key) { + throw new Error('Missing x-only public key') + } + + const receiveAddress = await getReceiveAddress() + + if (!receiveAddress) { + throw new Error('Missing receive address') + } + + const parsedAddress = Address.parse(receiveAddress, lwkNetwork) + + const scriptHashHex = parsedAddress.scriptPubkey().jet_sha256_hex() + + const scriptHash = Uint8Array.from( + scriptHashHex.match(/.{1,2}/g)!.map(byte => Number.parseInt(byte, 16)), + ) + + const scriptAuthProgram = loadScriptAuthProgram(scriptHash) + + const covenantAddress = scriptAuthProgram.createP2trAddress(key, lwkNetwork) + + const covenantUtxos = await fetchAddressUtxo(covenantAddress.toString()) + const walletUtxos = await getWalletUtxos() + const policyAsset = lwkNetwork.policyAsset().toString() + const walletUtxo = walletUtxos.find( + utxo => utxo.unblinded().asset().toString() === policyAsset, + ) + + if (!walletUtxo) { + throw new Error('Missing wallet L-BTC UTXO') + } + + const walletUtxoValue = walletUtxo.unblinded().value() + const feeReserve = 10_000n + const feeRate = 100 + + if (walletUtxoValue <= feeReserve) { + throw new Error('Selected wallet L-BTC UTXO is too small for fee reserve') + } + + const builtPset = buildExplicitRecipientPset({ + wollet: getWollet(), + network: lwkNetwork, + recipientAddress: covenantAddress.toString(), + outpoints: [walletUtxo.outpoint()], + satoshi: walletUtxoValue - feeReserve, + feeRate, + }) + const psetString = builtPset.toString() + + const tx = builtPset.extractTx() + + const txOutputs = tx.outputs.map( + (output): TxOutputSummary => ({ + asset: output?.asset()?.toString(), + amount: output.value()?.toString(), + script: output.scriptPubkey().toString(), + }), + ) + + if (cancelled) { + return + } + + setXOnlyPublicKey(key) + setPset(builtPset) + setPsetString(psetString) + + setResult({ + sourceLoaded: !!sources.script_auth, + + xOnlyPublicKey: key.toString(), + + receiveAddress, + + scriptHash: Array.from(scriptHash), + + address: covenantAddress.toString(), + + cmr: scriptAuthProgram.cmr.toString(), + + txOutputs, + + utxos: { + covenant: covenantUtxos, + wallet: walletUtxos.map(summarizeWalletUtxo), + }, + }) + } catch (err) { + if (!cancelled) { + setError(String(err)) + } + } + } + + void run() + return () => { cancelled = true } - }, [connectionStatus, getXOnlyPublicKey]) + }, [ + connectionStatus, + getReceiveAddress, + getWollet, + getWalletUtxos, + getXOnlyPublicKey, + lwkNetwork, + ]) + + useEffect(() => { + if (!broadcastTxid || txConfirmations !== null) { + return + } + + const id = setInterval(() => { + fetchTxConfirmations(broadcastTxid) + .then(confirmations => { + if (confirmations !== null && confirmations >= 1) { + setTxConfirmations(confirmations) + clearInterval(id) + } + }) + .catch(console.warn) + }, 15_000) + + return () => clearInterval(id) + }, [broadcastTxid, txConfirmations]) + + const explorerUrl = broadcastTxid ? getTxExplorerUrl(broadcastTxid) : null + + const handleSignAndBroadcast = async () => { + if (!pset) { + return + } + + setBroadcasting(true) + setBroadcastError(null) + setBroadcastTxid(null) - const result = useMemo(() => { - if (!xOnlyPublicKey) { - return null + try { + const txid = await signAndBroadcast(pset) + setBroadcastTxid(txid) + setTxConfirmations(null) + } catch (err) { + setBroadcastError(err instanceof Error ? err.message : String(err)) + } finally { + setBroadcasting(false) } + } + + const handleSpendCovenant = async () => { + if (!result) { + return + } + + setBroadcasting(true) + setBroadcastError(null) + setBroadcastTxid(null) + + try { + const [covenantUtxo] = await fetchAddressUtxo(result.address) + + if (!covenantUtxo?.value || !covenantUtxo.asset) { + throw new Error('Missing explicit covenant L-BTC UTXO') + } + + const policyAsset = lwkNetwork.policyAsset() + + if (covenantUtxo.asset !== policyAsset.toString()) { + throw new Error('Covenant UTXO asset is not L-BTC') + } - const scriptHash = crypto.getRandomValues(new Uint8Array(32)) - const program = loadScriptAuthProgram(scriptHash) - const address = program.createP2trAddress(xOnlyPublicKey, lwkNetwork).toString() + const walletUtxo = (await getWalletUtxos()).find( + utxo => utxo.unblinded().asset().toString() === policyAsset.toString(), + ) - return { - sourceLoaded: !!sources.script_auth, - xOnlyPublicKey: xOnlyPublicKey.toString(), - scriptHash: Array.from(scriptHash), - address, - cmr: program.cmr.toString(), + if (!walletUtxo) { + throw new Error('Missing wallet L-BTC UTXO for fees') + } + + const previousTx = Transaction.fromBytes(await fetchTxRaw(covenantUtxo.txid)) + const externalUtxo = new ExternalUtxo( + covenantUtxo.vout, + previousTx, + TxOutSecrets.fromExplicit(policyAsset, BigInt(covenantUtxo.value)), + 2_000, + true, + ) + const spendPset = buildExternalExplicitRecipientPset({ + wollet: getWollet(), + network: lwkNetwork, + recipientAddress: result.receiveAddress, + outpoints: [walletUtxo.outpoint()], + externalUtxos: [externalUtxo], + satoshi: BigInt(covenantUtxo.value), + feeRate: 100, + }) + + setPset(spendPset) + setPsetString(spendPset.toString()) + } catch (err) { + setBroadcastError(err instanceof Error ? err.message : String(err)) + } finally { + setBroadcasting(false) } - }, [lwkNetwork, xOnlyPublicKey]) + } return (
ScriptAuth Covenant Smoke Test
-
+        
+ + + +
+ + {broadcastError &&

{broadcastError}

} + +
           {JSON.stringify(
             {
+              pset: psetString,
               hasPubkey: !!xOnlyPublicKey,
               error,
+              broadcastError,
               result,
             },
             null,
             2,
           )}
         
+ {broadcastTxid && ( +
+
Broadcasted
+ +
TXID: {broadcastTxid}
+ + + Open in Explorer + +

+ {txConfirmations !== null + ? `${txConfirmations} confirmation${txConfirmations === 1 ? '' : 's'}` + : 'Waiting for confirmation...'} +

+
+ )}
) diff --git a/web-v2/src/pages/Dashboard/WalletDemo.tsx b/web-v2/src/pages/Dashboard/WalletDemo.tsx index 2201a53..ccf8e1c 100644 --- a/web-v2/src/pages/Dashboard/WalletDemo.tsx +++ b/web-v2/src/pages/Dashboard/WalletDemo.tsx @@ -32,7 +32,7 @@ export function WalletDemo() { usbDeviceDetected, connect, sendLbtc, - getLastReceiveAddress, + getReceiveAddress, verifyReceiveAddress, getXOnlyPublicKey, connectorId, @@ -47,7 +47,7 @@ export function WalletDemo() { const [txConfirmations, setTxConfirmations] = useState(null) const [verifyingAddress, setVerifyingAddress] = useState(false) const [xOnlyPubKey, setXOnlyPubKey] = useState(null) - const [lastReceiveAddress, setLastReceiveAddress] = useState(null) + const [receiveAddress, setReceiveAddress] = useState(null) useEffect(() => { if (connectionStatus !== 'ready') return @@ -65,15 +65,15 @@ export function WalletDemo() { useEffect(() => { if (connectionStatus !== 'ready') return let cancelled = false - getLastReceiveAddress() + getReceiveAddress() .then(addr => { - if (!cancelled) setLastReceiveAddress(addr) + if (!cancelled) setReceiveAddress(addr) }) .catch(console.warn) return () => { cancelled = true } - }, [connectionStatus, getLastReceiveAddress]) + }, [connectionStatus, getReceiveAddress]) // Poll Esplora directly for first confirmation after sending. useEffect(() => { @@ -203,7 +203,7 @@ export function WalletDemo() {

Receive address

- {lastReceiveAddress} + {receiveAddress} - - -
- - {broadcastError &&

{broadcastError}

} - -
-          {JSON.stringify(
-            {
-              pset: psetString,
-              hasPubkey: !!xOnlyPublicKey,
-              error,
-              broadcastError,
-              result,
-            },
-            null,
-            2,
-          )}
-        
- {broadcastTxid && ( -
-
Broadcasted
- -
TXID: {broadcastTxid}
- - - Open in Explorer - -

- {txConfirmations !== null - ? `${txConfirmations} confirmation${txConfirmations === 1 ? '' : 's'}` - : 'Waiting for confirmation...'} -

-
- )} -
-
- ) -} diff --git a/web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx b/web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx new file mode 100644 index 0000000..1f9cb1e --- /dev/null +++ b/web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx @@ -0,0 +1,409 @@ +// This file is a temporary playground for testing ScriptAuth covenants in a real wallet environment. +// It is not intended to be a long-term part of the codebase, and may be deleted or significantly refactored in the future. +// The demo performs the following steps: +// 1. User clicks "Fund Covenant UTXO" button and waits at least 1 confirmation +// 2. User clicks "Spend Covenant UTXO" button, which attempts to spend the covenant UTXO using the auth UTXO and logs the result +import { + Address, + ExternalUtxo, + SimplicityLogLevel, + Transaction, + TxBuilder, + TxOutSecrets, + type XOnlyPublicKey, +} from 'lwk_web' +import { useState } from 'react' + +import { broadcastTx, fetchAddressUtxo, fetchTxRaw } from '@/api/esplora/methods' +import { getTxExplorerUrl } from '@/api/esplora/utils' +import { findUtxoByOutpoint, outpointToString } from '@/lwk/utxo' +import { useLwk } from '@/providers/lwk/useLwk' +import { useWallet } from '@/providers/wallet/useWallet' +import { + latestScriptAuthState, + saveScriptAuthState, + selectDemoScriptAuthInputs, + useTxConfirmations, +} from '@/simplicity/script-auth/helpers' +import { buildScriptAuthWitness, loadScriptAuthProgram } from '@/simplicity/script-auth/program' +import { hexToBytes } from '@/utils/hex' + +interface FundingSummary { + covenantAddress: string + fundingOutpoint: string + authOutpoint: string + scriptHashHex: string + amount: string +} + +interface SpendSummary { + covenantOutpoint: string + authOutpoint: string + recipientAddress: string + amount: string +} + +interface BroadcastState { + busy: boolean + error: string | null + summary: TSummary | null + txid: string | null +} + +const DEFAULT_SCRIPT_AUTH_FEE_RATE = 100 +const DEFAULT_SCRIPT_AUTH_FEE_RESERVE = 10_000n +const DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY = 20_000 + +const INITIAL_BROADCAST_STATE = { + busy: false, + error: null, + summary: null, + txid: null, +} + +export default function ScriptAuthCovenantDemo() { + const { lwkNetwork } = useLwk() + const { + connectionStatus, + getXOnlyPublicKey, + getReceiveAddress, + getWalletUtxos, + getWollet, + signPset, + } = useWallet() + + const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) + + const [fundingState, setFundingState] = useState>({ + ...INITIAL_BROADCAST_STATE, + }) + const [spendState, setSpendState] = useState>({ + ...INITIAL_BROADCAST_STATE, + }) + + const fundingConfirmations = useTxConfirmations(fundingState.txid) + const spendConfirmations = useTxConfirmations(spendState.txid) + + const fundCovenant = async () => { + setFundingState(state => ({ + ...state, + busy: true, + error: null, + summary: null, + txid: null, + })) + + try { + const key = await getXOnlyPublicKey() + if (!key) { + throw new Error('Missing x-only public key') + } + setXOnlyPublicKey(key) + + const walletUtxos = await getWalletUtxos() + const policyAsset = lwkNetwork.policyAsset() + const { authUtxo, fundingUtxo } = selectDemoScriptAuthInputs( + walletUtxos, + policyAsset, + DEFAULT_SCRIPT_AUTH_FEE_RESERVE, + ) + const scriptHashHex = authUtxo.scriptPubkey().jet_sha256_hex() + const scriptAuthProgram = loadScriptAuthProgram(hexToBytes(scriptHashHex)) + const scriptAuthAddress = scriptAuthProgram.createP2trAddress(key, lwkNetwork) + const covenantAddress = scriptAuthAddress.toString() + + const fundingAmount = fundingUtxo.unblinded().value() - DEFAULT_SCRIPT_AUTH_FEE_RESERVE + const fundingOutpoint = outpointToString(fundingUtxo) + + const authOutpoint = outpointToString(authUtxo) + + const wollet = await getWollet() + if (!wollet) { + throw new Error('Wallet not connected') + } + + const fundingPset = new TxBuilder(lwkNetwork) + .feeRate(DEFAULT_SCRIPT_AUTH_FEE_RATE) + .setWalletUtxos([fundingUtxo.outpoint()]) + .addExplicitRecipient(scriptAuthAddress, fundingAmount, policyAsset) + .finish(wollet) + + const signedPset = await signPset(fundingPset) + const finalizedPset = wollet.finalize(signedPset) + const fundingTx = finalizedPset.extractTx() + const fundingTxid = await broadcastTx(fundingTx.toString()) + + saveScriptAuthState({ + authOutpoint, + scriptHashHex, + fundingTxid, + }) + + setFundingState({ + busy: false, + error: null, + summary: { + covenantAddress, + fundingOutpoint, + authOutpoint, + scriptHashHex, + amount: fundingAmount.toString(), + }, + txid: fundingTxid, + }) + } catch (err) { + setFundingState(state => ({ + ...state, + busy: false, + error: err instanceof Error ? err.message : String(err), + })) + } + } + + const spendCovenantUtxo = async () => { + setSpendState(state => ({ + ...state, + busy: true, + error: null, + summary: null, + txid: null, + })) + + try { + const scriptAuthState = latestScriptAuthState() + if (!scriptAuthState) { + throw new Error('Failed to prepare ScriptAuth state') + } + const { scriptHashHex } = scriptAuthState + + const scriptAuthProgram = loadScriptAuthProgram(hexToBytes(scriptHashHex)) + const key = xOnlyPublicKey ?? (await getXOnlyPublicKey()) + if (!key) { + throw new Error('Missing x-only public key') + } + setXOnlyPublicKey(key) + + const scriptAuthAddress = scriptAuthProgram.createP2trAddress(key, lwkNetwork) + const scriptAuthAddressString = scriptAuthAddress.toString() + + const covenantUtxos = await fetchAddressUtxo(scriptAuthAddressString) + const covenantUtxo = + covenantUtxos.find(utxo => utxo.txid === scriptAuthState.fundingTxid) ?? covenantUtxos[0] + if (!covenantUtxo) { + throw new Error('ScriptAuth covenant UTXO not found') + } + + const walletUtxos = await getWalletUtxos() + + const authUtxo = findUtxoByOutpoint(walletUtxos, scriptAuthState.authOutpoint) + if (!authUtxo) { + throw new Error('ScriptAuth auth UTXO not found') + } + const authOutpointString = outpointToString(authUtxo) + const covenantOutpoint = `${covenantUtxo.txid}:${covenantUtxo.vout}` + + const covenantTx = Transaction.fromBytes(await fetchTxRaw(covenantUtxo.txid)) + const covenantTxOut = covenantTx.outputs[covenantUtxo.vout] + if (!covenantTxOut) { + throw new Error('ScriptAuth covenant funding transaction does not have the UTXO output') + } + + const authOutpoint = authUtxo.outpoint() + const authTx = Transaction.fromBytes(await fetchTxRaw(authOutpoint.txid().toString())) + const authTxOut = authTx.outputs[authOutpoint.vout()] + + if (!authTxOut) { + throw new Error('ScriptAuth auth transaction does not have the selected output') + } + + const policyAsset = lwkNetwork.policyAsset() + const covenantValue = covenantUtxo.value + + if (!covenantValue) { + throw new Error('ScriptAuth covenant UTXO value is missing') + } + + const covenantExternalUtxo = new ExternalUtxo( + covenantUtxo.vout, + covenantTx, + TxOutSecrets.fromExplicit(policyAsset, BigInt(covenantValue)), + DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, + true, + ) + + const receiveAddress = await getReceiveAddress() + if (!receiveAddress) { + throw new Error('Missing receive address') + } + + const wollet = await getWollet() + if (!wollet) { + throw new Error('Wallet not connected') + } + + const recipientAddress = Address.parse(receiveAddress, lwkNetwork).toUnconfidential() + const recipientAddressString = recipientAddress.toString() + const spendPset = new TxBuilder(lwkNetwork) + .feeRate(DEFAULT_SCRIPT_AUTH_FEE_RATE) + .setWalletUtxos([authOutpoint]) + .addExternalUtxos([covenantExternalUtxo]) + .addExplicitRecipient(recipientAddress, BigInt(covenantValue), policyAsset) + .finish(wollet) + + const signedSpendPset = await signPset(spendPset) + const finalizedWalletPset = wollet.finalize(signedSpendPset) + const txWithWalletWitness = finalizedWalletPset.extractTx() + + const COVENANT_INPUT_INDEX = 0 + const AUTH_INPUT_INDEX = 1 + + const finalizedTx = scriptAuthProgram.finalizeTransaction( + txWithWalletWitness, + key, + [covenantTxOut, authTxOut], + COVENANT_INPUT_INDEX, + buildScriptAuthWitness(AUTH_INPUT_INDEX), + lwkNetwork, + SimplicityLogLevel.Trace, + ) + + const spendTxid = await broadcastTx(finalizedTx.toString()) + + setSpendState({ + busy: false, + error: null, + summary: { + covenantOutpoint, + authOutpoint: authOutpointString, + recipientAddress: recipientAddressString, + amount: covenantValue.toString(), + }, + txid: spendTxid, + }) + } catch (err) { + setSpendState(state => ({ + ...state, + busy: false, + error: err instanceof Error ? err.message : String(err), + })) + } + } + + const busy = fundingState.busy || spendState.busy + const disabled = connectionStatus !== 'ready' || busy + + return ( +
+
+
ScriptAuth Covenant Smoke Test
+ +
+ + + +
+ + {fundingState.error && ( +

Funding: {fundingState.error}

+ )} + {spendState.error &&

Spend: {spendState.error}

} + +
+ + + +
+ +
+          {JSON.stringify(
+            {
+              connectionStatus,
+              hasPubkey: !!xOnlyPublicKey,
+              latestSavedState: latestScriptAuthState(),
+              funding: {
+                broadcasting: fundingState.busy,
+                txid: fundingState.txid,
+                confirmations: fundingConfirmations,
+                error: fundingState.error,
+              },
+              spend: {
+                broadcasting: spendState.busy,
+                txid: spendState.txid,
+                confirmations: spendConfirmations,
+                error: spendState.error,
+              },
+            },
+            null,
+            2,
+          )}
+        
+
+
+ ) +} +function BroadcastResult({ + title, + txid, + confirmations, + summary, +}: { + title: string + txid: string | null + confirmations: number | null + summary?: unknown +}) { + if (!txid) { + return null + } + + return ( +
+
{title}
+ +
TXID: {txid}
+ + + Open in Explorer + + +

+ {confirmations !== null + ? `${confirmations} confirmation${confirmations === 1 ? '' : 's'}` + : 'Waiting for confirmation...'} +

+ + {summary !== undefined && ( +
+          {JSON.stringify(summary, null, 2)}
+        
+ )} +
+ ) +} diff --git a/web-v2/src/pages/Dashboard/index.tsx b/web-v2/src/pages/Dashboard/index.tsx index 322f0ee..8f1c178 100644 --- a/web-v2/src/pages/Dashboard/index.tsx +++ b/web-v2/src/pages/Dashboard/index.tsx @@ -1,4 +1,4 @@ -import CovenantDemo from './CovenantDemo' +import ScriptAuthCovenantDemo from './ScriptAuthCovenantDemo' import { WalletDemo } from './WalletDemo' export default function DashboardPage() { @@ -6,7 +6,7 @@ export default function DashboardPage() {

Dashboard

- +
) } diff --git a/web-v2/src/providers/wallet/WalletProvider.tsx b/web-v2/src/providers/wallet/WalletProvider.tsx index 51b8983..e812867 100644 --- a/web-v2/src/providers/wallet/WalletProvider.tsx +++ b/web-v2/src/providers/wallet/WalletProvider.tsx @@ -1,7 +1,6 @@ -import { Address, type Pset, TxBuilder, Wollet, type XOnlyPublicKey } from 'lwk_web' +import { type Pset, Wollet, type XOnlyPublicKey } from 'lwk_web' import { useCallback, useEffect, useRef, useState } from 'react' -import { broadcastTx } from '@/api/esplora/methods' import { env } from '@/constants/env' import { useSessionStorage } from '@/hooks/useSessionStorage' import { JadeConnector } from '@/lib/wallet-core/connector/jade' @@ -247,28 +246,6 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { return session.connector.signPset(pset) }, []) - const signAndBroadcast = useCallback( - async (pset: Pset): Promise => { - const session = sessionRef.current - if (!session) throw new Error('WalletProvider: not connected') - - const signedPset = await signPset(pset) - const finalizedPset = session.wollet.finalize(signedPset) - const txHex = finalizedPset.extractTx().toString() - const txidStr = await broadcastTx(txHex) - - // Auto-sync balances after broadcast (fire-and-forget, errors are non-fatal). - syncBalances(session.wollet, session.esploraClient) - .then(balances => { - setState(s => ({ ...s, balances })) - }) - .catch(console.warn) - - return txidStr - }, - [signPset], - ) - const getReceiveAddress = useCallback(async (): Promise => { const session = sessionRef.current if (!session) return null @@ -300,29 +277,13 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { return session.wollet.utxos() }, []) - const getWollet = useCallback((): Wollet => { + const getWollet = useCallback(async (): Promise => { const session = sessionRef.current - - if (!session) { - throw new Error('WalletProvider: not connected') - } + if (!session) return null return session.wollet }, []) - const sendLbtc = useCallback( - async (recipientAddress: string, satoshi: bigint): Promise => { - const session = sessionRef.current - if (!session) throw new Error('WalletProvider: not connected') - - const addr = Address.parse(recipientAddress, lwkNetwork) - const txBuilder = await new TxBuilder(lwkNetwork).feeRate(100).addLbtcRecipient(addr, satoshi) - const pset = txBuilder.finish(session.wollet) - return signAndBroadcast(pset) - }, - [lwkNetwork, signAndBroadcast], - ) - return ( diff --git a/web-v2/src/providers/wallet/types.ts b/web-v2/src/providers/wallet/types.ts index 45a937a..8a1aff0 100644 --- a/web-v2/src/providers/wallet/types.ts +++ b/web-v2/src/providers/wallet/types.ts @@ -15,15 +15,11 @@ export interface WalletContextValue extends WalletState { disconnect(): Promise syncWallet(): Promise signPset(pset: Pset): Promise - signAndBroadcast(pset: Pset): Promise // TODO: Remove this method - sendLbtc(recipientAddress: string, satoshi: bigint): Promise getWalletUtxos(): Promise - getWollet(): Wollet + getWollet(): Promise getReceiveAddress(): Promise verifyReceiveAddress(): Promise getXOnlyPublicKey(): Promise - resumeSession(): Promise - savedSession: SavedSession | null } export interface WalletSession { diff --git a/web-v2/src/simplicity/script-auth/helpers.ts b/web-v2/src/simplicity/script-auth/helpers.ts new file mode 100644 index 0000000..6b887d3 --- /dev/null +++ b/web-v2/src/simplicity/script-auth/helpers.ts @@ -0,0 +1,135 @@ +import type { AssetId, WalletTxOut } from 'lwk_web' +import { useEffect, useState } from 'react' + +import { fetchTxConfirmations } from '@/api/esplora/methods' +import { getPolicyAssetUtxos, outpointToString } from '@/lwk/utxo' + +export interface DemoScriptAuthInputSelection { + authUtxo: WalletTxOut + fundingUtxo: WalletTxOut +} + +/** + * Temporary input selection strategy used by + * ScriptAuth smoke tests and demo flows. + * + * Production covenant creation will use + * explicit auth/funding UTXO selection. + */ +export function selectDemoScriptAuthInputs( + walletUtxos: WalletTxOut[], + policyAsset: AssetId | string, + feeReserve: bigint, +): DemoScriptAuthInputSelection { + const lbtcUtxos = getPolicyAssetUtxos(walletUtxos, policyAsset) + + const fundingUtxo = lbtcUtxos + .filter(utxo => utxo.unblinded().value() > feeReserve) + .sort((a, b) => { + const aValue = a.unblinded().value() + const bValue = b.unblinded().value() + if (aValue === bValue) return 0 + return bValue > aValue ? 1 : -1 + })[0] + + if (!fundingUtxo) { + throw new Error('Need a wallet L-BTC UTXO larger than the fee reserve to fund ScriptAuth') + } + + const fundingOutpoint = outpointToString(fundingUtxo) + const authUtxo = lbtcUtxos.find(utxo => outpointToString(utxo) !== fundingOutpoint) + + if (!authUtxo) { + throw new Error('Need a second wallet L-BTC UTXO to use as the ScriptAuth auth input') + } + + return { authUtxo, fundingUtxo } +} + +export interface SavedScriptAuthState { + authOutpoint: string + scriptHashHex: string + fundingTxid: string +} + +export function latestScriptAuthState() { + const states = getScriptAuthStates() + return states[states.length - 1] ?? null +} + +export function useTxConfirmations(txid: string | null): number | null { + const [confirmedTx, setConfirmedTx] = useState<{ + confirmations: number + txid: string + } | null>(null) + + useEffect(() => { + if (!txid) { + return + } + + let cancelled = false + let intervalId: ReturnType | null = null + + const poll = async () => { + try { + const nextConfirmations = await fetchTxConfirmations(txid) + + if (cancelled) { + return + } + + if (nextConfirmations !== null && nextConfirmations >= 1) { + setConfirmedTx({ confirmations: nextConfirmations, txid }) + if (intervalId) { + clearInterval(intervalId) + } + } + } catch (err) { + console.warn(err) + } + } + + poll() + intervalId = setInterval(() => { + poll() + }, 15_000) + + return () => { + cancelled = true + if (intervalId) { + clearInterval(intervalId) + } + } + }, [txid]) + + return confirmedTx?.txid === txid ? confirmedTx.confirmations : null +} + +const SCRIPT_AUTH_STORAGE_KEY = 'script-auth-covenants' + +export function getScriptAuthStates(): SavedScriptAuthState[] { + const raw = localStorage.getItem(SCRIPT_AUTH_STORAGE_KEY) + + if (!raw) { + return [] + } + + return JSON.parse(raw) +} + +export function saveScriptAuthState(state: SavedScriptAuthState): void { + const existingStates = getScriptAuthStates() + + existingStates.push(state) + + localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(existingStates)) +} + +export function removeScriptAuthState(authOutpoint: string): void { + const existingStates = getScriptAuthStates() + + const filteredStates = existingStates.filter(state => state.authOutpoint !== authOutpoint) + + localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(filteredStates)) +} diff --git a/web-v2/src/simplicity/covenants/scriptAuth.ts b/web-v2/src/simplicity/script-auth/program.ts similarity index 92% rename from web-v2/src/simplicity/covenants/scriptAuth.ts rename to web-v2/src/simplicity/script-auth/program.ts index 630229d..a71e4e3 100644 --- a/web-v2/src/simplicity/covenants/scriptAuth.ts +++ b/web-v2/src/simplicity/script-auth/program.ts @@ -8,11 +8,6 @@ import { sources } from 'virtual:simplicity-sources' import { bytes32ToHex } from '@/utils/hex' -export function loadScriptAuthProgram(scriptHash: Uint8Array): SimplicityProgram { - return SimplicityProgram.load(sources.script_auth, buildScriptAuthArguments(scriptHash)) -} - -// TODO: Generate typed argument/witness names from Simplicity source code const ARGUMENTS = { SCRIPT_HASH: 'SCRIPT_HASH', } as const @@ -21,6 +16,10 @@ const WITNESS = { INPUT_SCRIPT_INDEX: 'INPUT_SCRIPT_INDEX', } as const +export function loadScriptAuthProgram(scriptHash: Uint8Array): SimplicityProgram { + return SimplicityProgram.load(sources.script_auth, buildScriptAuthArguments(scriptHash)) +} + export function buildScriptAuthArguments(scriptHash: Uint8Array): SimplicityArguments { return new SimplicityArguments().addValue( ARGUMENTS.SCRIPT_HASH, From 29782f4d91fb6a6bc3bfb239316665f6cc0f600e Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Tue, 2 Jun 2026 18:10:40 +0300 Subject: [PATCH 07/16] feat: add issuance factory program and borrower account demo --- web-v2/Dockerfile | 2 +- web-v2/simplicity-covenants.config.json | 3 +- .../Dashboard/CreateBorrowerAccountDemo.tsx | 387 ++++++++++++++++++ web-v2/src/pages/Dashboard/index.tsx | 2 + .../simplicity/issuance-factory/program.ts | 84 ++++ web-v2/src/utils/hex.ts | 13 +- web-v2/src/utils/uint.ts | 15 + web-v2/src/vite-env.d.ts | 1 + 8 files changed, 503 insertions(+), 4 deletions(-) create mode 100644 web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx create mode 100644 web-v2/src/simplicity/issuance-factory/program.ts create mode 100644 web-v2/src/utils/uint.ts diff --git a/web-v2/Dockerfile b/web-v2/Dockerfile index 3c426d9..ad9a0ad 100644 --- a/web-v2/Dockerfile +++ b/web-v2/Dockerfile @@ -4,7 +4,7 @@ FROM rust:1.91-bookworm AS lwk-builder ARG LWK_REF=wasm-0.16-expose-external-utxos # ARG LWK_COMMIT=4786e456db6efc05413721f6489899a6426eaa47 -ARG LWK_COMMIT=133aeb6eea0a26af026ad80d27504db5afa50384 +ARG LWK_COMMIT=9f1faea1a6765b7591a8377834be38f4a8f090ae RUN apt-get update && apt-get install -y --no-install-recommends \ git pkg-config libssl-dev ca-certificates clang \ diff --git a/web-v2/simplicity-covenants.config.json b/web-v2/simplicity-covenants.config.json index 100383c..5c9c0fe 100644 --- a/web-v2/simplicity-covenants.config.json +++ b/web-v2/simplicity-covenants.config.json @@ -2,6 +2,7 @@ "covenants": [ { "id": "lending", "path": "../crates/contracts/simf/lending.simf" }, { "id": "asset_auth", "path": "../crates/contracts/simf/asset_auth.simf" }, - { "id": "script_auth", "path": "../crates/contracts/simf/script_auth.simf" } + { "id": "script_auth", "path": "../crates/contracts/simf/script_auth.simf" }, + { "id": "issuance_factory", "path": "../crates/contracts/simf/issuance_factory.simf" } ] } diff --git a/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx b/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx new file mode 100644 index 0000000..330e448 --- /dev/null +++ b/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx @@ -0,0 +1,387 @@ +import { + Address, + assetIdFromIssuance, + ContractHash, + IssuanceRecipient, + Script, + TxBuilder, + type XOnlyPublicKey, +} from 'lwk_web' +import { useState } from 'react' + +import { broadcastTx } from '@/api/esplora/methods' +import { getTxExplorerUrl } from '@/api/esplora/utils' +import { getPolicyAssetUtxos, outpointToString } from '@/lwk/utxo' +import { useLwk } from '@/providers/lwk/useLwk' +import { useWallet } from '@/providers/wallet/useWallet' +import { loadIssuanceFactoryProgram } from '@/simplicity/issuance-factory/program' +import { useTxConfirmations } from '@/simplicity/script-auth/helpers' +import { bytesToHex } from '@/utils/hex' + +interface CreateBorrowerAccountSummary { + fundingOutpoint: string + factoryAddress: string + factoryAuthOutpoint: string + issuanceFactoryOutpoint: string + issuedAssetId: string + issuanceAmount: string + reissuanceTokenAmount: string + metadataOpReturnHex: string + metadataIncluded: boolean +} + +interface RemoveBorrowerAccountSummary { + factoryOutpoint: string +} + +interface BorrowerAccountState { + creationTxid: string + factoryAddress: string + factoryAuthOutpoint: string + issuanceFactoryOutpoint: string + issuedAssetId: string + metadataOpReturnHex: string +} + +interface BroadcastState { + busy: boolean + error: string | null + summary: TSummary | null + txid: string | null +} + +const BORROWER_ACCOUNT_STORAGE_KEY = 'borrower-account-demo' +const DEFAULT_FEE_RESERVE = 10_000n +const ISSUING_UTXOS_COUNT = 2 +const REISSUANCE_FLAGS = 0n +const ISSUANCE_AMOUNT = 2n +const REISSUANCE_TOKEN_AMOUNT = 0n +const FACTORY_AUTH_AMOUNT = 1n +const ISSUANCE_FACTORY_AMOUNT = 1n + +const INITIAL_BROADCAST_STATE = { + busy: false, + error: null, + summary: null, + txid: null, +} + +export default function CreateBorrowerAccountDemo() { + const { lwkNetwork } = useLwk() + const { + connectionStatus, + getReceiveAddress, + getWalletUtxos, + getWollet, + getXOnlyPublicKey, + signPset, + } = useWallet() + + const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) + const [createState, setCreateState] = useState>({ + ...INITIAL_BROADCAST_STATE, + }) + const [removeState, setRemoveState] = useState>({ + ...INITIAL_BROADCAST_STATE, + }) + + const createConfirmations = useTxConfirmations(createState.txid) + const removeConfirmations = useTxConfirmations(removeState.txid) + + const createBorrowerAccount = async () => { + setCreateState(state => ({ + ...state, + busy: true, + error: null, + summary: null, + txid: null, + })) + + try { + const key = await getXOnlyPublicKey() + if (!key) { + throw new Error('Missing x-only public key') + } + setXOnlyPublicKey(key) + + const receiveAddressString = await getReceiveAddress() + if (!receiveAddressString) { + throw new Error('Missing receive address') + } + + const wollet = await getWollet() + if (!wollet) { + throw new Error('Wallet not connected') + } + + const policyAsset = lwkNetwork.policyAsset() + const walletUtxos = await getWalletUtxos() + const feeUtxo = getPolicyAssetUtxos(walletUtxos, policyAsset) + .filter(utxo => utxo.unblinded().value() > DEFAULT_FEE_RESERVE) + .sort((a, b) => Number(a.unblinded().value() - b.unblinded().value()))[0] + + if (!feeUtxo) { + throw new Error('Need a wallet L-BTC UTXO larger than the fee reserve') + } + + const fundingOutpoint = outpointToString(feeUtxo) + const receiveAddress = Address.parse(receiveAddressString, lwkNetwork).toUnconfidential() + const issuanceFactoryProgram = loadIssuanceFactoryProgram({ + issuingUtxosCount: ISSUING_UTXOS_COUNT, + reissuanceFlags: REISSUANCE_FLAGS, + factoryOwnerPubkey: key.toBytes(), + }) + const factoryAddress = issuanceFactoryProgram.createP2trAddress(key, lwkNetwork) + const factoryAddressString = factoryAddress.toString() + const issuedAssetId = assetIdFromIssuance(feeUtxo.outpoint(), emptyContractHash()) + + const metadata = await buildIssuanceFactoryMetadata() + + if (FACTORY_AUTH_AMOUNT + ISSUANCE_FACTORY_AMOUNT !== ISSUANCE_AMOUNT) { + throw new Error('invalid issuance split') + } + + const issuanceRecipients = [ + IssuanceRecipient.fromAddress(FACTORY_AUTH_AMOUNT, receiveAddress), + IssuanceRecipient.fromAddress(ISSUANCE_FACTORY_AMOUNT, factoryAddress), + ] + + const pset = new TxBuilder(lwkNetwork) + .setWalletUtxos([feeUtxo.outpoint()]) + .issueAssetToRecipients(issuanceRecipients, REISSUANCE_TOKEN_AMOUNT, null, null) + .addExplicitScriptOutput(Script.newOpReturn(metadata), 0n, policyAsset) + .finish(wollet) + + const signedPset = await signPset(pset) + const finalizedPset = wollet.finalize(signedPset) + const tx = finalizedPset.extractTx() + const creationTxid = await broadcastTx(tx.toString()) + + const savedState: BorrowerAccountState = { + creationTxid, + factoryAddress: factoryAddressString, + factoryAuthOutpoint: `${creationTxid}:0`, + issuanceFactoryOutpoint: `${creationTxid}:1`, + issuedAssetId: issuedAssetId.toString(), + metadataOpReturnHex: bytesToHex(Script.newOpReturn(metadata).bytes()), + } + saveBorrowerAccountState(savedState) + + setCreateState({ + busy: false, + error: null, + summary: { + fundingOutpoint, + factoryAddress: savedState.factoryAddress, + factoryAuthOutpoint: savedState.factoryAuthOutpoint, + issuanceFactoryOutpoint: savedState.issuanceFactoryOutpoint, + issuedAssetId: savedState.issuedAssetId, + issuanceAmount: ISSUANCE_AMOUNT.toString(), + reissuanceTokenAmount: REISSUANCE_TOKEN_AMOUNT.toString(), + metadataOpReturnHex: savedState.metadataOpReturnHex, + metadataIncluded: true, + }, + txid: creationTxid, + }) + } catch (err) { + setCreateState(state => ({ + ...state, + busy: false, + error: err instanceof Error ? err.message : String(err), + })) + } + } + + const removeBorrowerAccount = async () => { + setRemoveState(state => ({ + ...state, + busy: true, + error: null, + summary: null, + txid: null, + })) + + try { + if (!latestBorrowerAccountState()) { + throw new Error('Create a borrower account first') + } + + throw new Error( + 'Remove is scaffolded but not wired: the wallet connector must expose Schnorr signing for IssuanceFactory sig_all_hash.', + ) + } catch (err) { + setRemoveState(state => ({ + ...state, + busy: false, + error: err instanceof Error ? err.message : String(err), + })) + } + } + + const busy = createState.busy || removeState.busy + const disabled = connectionStatus !== 'ready' || busy + + return ( +
+
+
Borrower Account IssuanceFactory Demo
+

+ Creates a borrower account by issuing two units of a new auth asset from one wallet L-BTC + input. One unit returns to the user as FactoryAuth, and one unit funds the IssuanceFactory + covenant. Reissuance token amount is zero. +

+ +
+ + + +
+ + {createState.error && ( +

Create: {createState.error}

+ )} + {removeState.error && ( +

Remove: {removeState.error}

+ )} + +
+ + + +
+ +
+          {JSON.stringify(
+            {
+              connectionStatus,
+              hasPubkey: !!xOnlyPublicKey,
+              latestSavedState: latestBorrowerAccountState(),
+              constants: {
+                issuingUtxosCount: ISSUING_UTXOS_COUNT,
+                reissuanceFlags: REISSUANCE_FLAGS.toString(),
+                issuanceAmount: ISSUANCE_AMOUNT.toString(),
+                reissuanceTokenAmount: REISSUANCE_TOKEN_AMOUNT.toString(),
+              },
+              create: {
+                broadcasting: createState.busy,
+                txid: createState.txid,
+                confirmations: createConfirmations,
+                error: createState.error,
+              },
+              remove: {
+                broadcasting: removeState.busy,
+                txid: removeState.txid,
+                confirmations: removeConfirmations,
+                error: removeState.error,
+              },
+            },
+            null,
+            2,
+          )}
+        
+
+
+ ) +} + +async function buildIssuanceFactoryMetadata(): Promise { + const programId = await getIssuanceFactoryProgramId() + const data = new Uint8Array(13) + data.set(programId, 0) + data[4] = ISSUING_UTXOS_COUNT + new DataView(data.buffer).setBigUint64(5, REISSUANCE_FLAGS, true) + return data +} + +function emptyContractHash(): ContractHash { + return ContractHash.fromBytes(new Uint8Array(32)) +} + +async function getIssuanceFactoryProgramId(): Promise { + const { sources } = await import('virtual:simplicity-sources') + const hash = await crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(sources.issuance_factory), + ) + return new Uint8Array(hash).slice(0, 4) +} + +function getBorrowerAccountStates(): BorrowerAccountState[] { + const raw = localStorage.getItem(BORROWER_ACCOUNT_STORAGE_KEY) + if (!raw) { + return [] + } + + return JSON.parse(raw) +} + +function latestBorrowerAccountState(): BorrowerAccountState | null { + const states = getBorrowerAccountStates() + return states[states.length - 1] ?? null +} + +function saveBorrowerAccountState(state: BorrowerAccountState): void { + const existingStates = getBorrowerAccountStates() + existingStates.push(state) + localStorage.setItem(BORROWER_ACCOUNT_STORAGE_KEY, JSON.stringify(existingStates)) +} + +function BroadcastResult({ + title, + txid, + confirmations, + summary, +}: { + title: string + txid: string | null + confirmations: number | null + summary?: unknown +}) { + if (!txid) { + return null + } + + return ( +
+
{title}
+ + {txid} + +
+ Confirmations: {confirmations === null ? 'waiting…' : confirmations} +
+ {summary ? ( +
+          {JSON.stringify(summary, null, 2)}
+        
+ ) : null} +
+ ) +} diff --git a/web-v2/src/pages/Dashboard/index.tsx b/web-v2/src/pages/Dashboard/index.tsx index 8f1c178..6958481 100644 --- a/web-v2/src/pages/Dashboard/index.tsx +++ b/web-v2/src/pages/Dashboard/index.tsx @@ -1,3 +1,4 @@ +import CreateBorrowerAccountDemo from './CreateBorrowerAccountDemo' import ScriptAuthCovenantDemo from './ScriptAuthCovenantDemo' import { WalletDemo } from './WalletDemo' @@ -6,6 +7,7 @@ export default function DashboardPage() {

Dashboard

+
) diff --git a/web-v2/src/simplicity/issuance-factory/program.ts b/web-v2/src/simplicity/issuance-factory/program.ts new file mode 100644 index 0000000..f304301 --- /dev/null +++ b/web-v2/src/simplicity/issuance-factory/program.ts @@ -0,0 +1,84 @@ +import { + SimplicityArguments, + SimplicityProgram, + SimplicityType, + SimplicityTypedValue, + SimplicityWitnessValues, +} from 'lwk_web' +import { sources } from 'virtual:simplicity-sources' + +import { bytes32ToHex, isHexStringOfByteLength, normalizeHex } from '@/utils/hex' +import { isUint8, isUint32, isUint64 } from '@/utils/uint' + +const ARGUMENTS = { + ISSUING_UTXOS_COUNT: 'ISSUING_UTXOS_COUNT', + REISSUANCE_FLAGS: 'REISSUANCE_FLAGS', + FACTORY_OWNER_PUBKEY: 'FACTORY_OWNER_PUBKEY', +} as const + +const WITNESS = { + PATH: 'PATH', +} as const + +export interface IssuanceFactoryProgramParams { + issuingUtxosCount: number + reissuanceFlags: bigint + factoryOwnerPubkey: Uint8Array +} + +export interface IssuanceFactoryWitnessParams { + branch: 'IssueAssets' | 'RemoveFactory' + outputIndex: number + ownerSignatureHex: string +} + +export function loadIssuanceFactoryProgram( + params: IssuanceFactoryProgramParams, +): SimplicityProgram { + return SimplicityProgram.load(sources.issuance_factory, buildIssuanceFactoryArguments(params)) +} + +export function buildIssuanceFactoryArguments( + params: IssuanceFactoryProgramParams, +): SimplicityArguments { + if (!isUint8(params.issuingUtxosCount)) { + throw new Error('issuingUtxosCount must fit into u8') + } + if (!isUint64(params.reissuanceFlags)) { + throw new Error('reissuanceFlags must fit into u64') + } + if (params.factoryOwnerPubkey.length !== 32) { + throw new Error('factoryOwnerPubkey must be a 32-byte x-only public key') + } + + return new SimplicityArguments() + .addValue(ARGUMENTS.ISSUING_UTXOS_COUNT, SimplicityTypedValue.fromU8(params.issuingUtxosCount)) + .addValue(ARGUMENTS.REISSUANCE_FLAGS, SimplicityTypedValue.fromU64(params.reissuanceFlags)) + .addValue( + ARGUMENTS.FACTORY_OWNER_PUBKEY, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.factoryOwnerPubkey)), + ) +} + +export function buildIssuanceFactoryWitness( + params: IssuanceFactoryWitnessParams, +): SimplicityWitnessValues { + if (!isUint32(params.outputIndex)) { + throw new Error('outputIndex must fit into u32') + } + + const pathType = SimplicityType.fromString('Either<(u32, Signature), (u32, Signature)>') + const ownerSignatureHex = normalizeHex(params.ownerSignatureHex) + if (!isHexStringOfByteLength(ownerSignatureHex, 64)) { + throw new Error('ownerSignatureHex must be a 64-byte Schnorr signature hex string') + } + + const pathParams = `(${params.outputIndex}, 0x${ownerSignatureHex})` + const pathExpression = + params.branch === 'IssueAssets' ? `Left(${pathParams})` : `Right(${pathParams})` + + return new SimplicityWitnessValues().addValue( + WITNESS.PATH, + SimplicityTypedValue.parse(pathExpression, pathType), + ) +} diff --git a/web-v2/src/utils/hex.ts b/web-v2/src/utils/hex.ts index cff9cb2..5de2c72 100644 --- a/web-v2/src/utils/hex.ts +++ b/web-v2/src/utils/hex.ts @@ -4,13 +4,22 @@ export function normalizeHex(hexInput: string): string { return hexInput.trim().toLowerCase().replace(/^0x/, '') } +function normalizeHexWithoutWhitespace(input: string): string { + return normalizeHex(input).replace(/\s/g, '') +} + export function isHexString(input: string): boolean { - const normalized = normalizeHex(input).replace(/\s/g, '') + const normalized = normalizeHexWithoutWhitespace(input) return normalized.length > 0 && normalized.length % 2 === 0 && HEX_PATTERN.test(normalized) } +export function isHexStringOfByteLength(input: string, byteLength: number): boolean { + const normalized = normalizeHexWithoutWhitespace(input) + return normalized.length === byteLength * 2 && isHexString(normalized) +} + export function hexToBytes(hexString: string): Uint8Array { - const normalized = normalizeHex(hexString).replace(/\s/g, '') + const normalized = normalizeHexWithoutWhitespace(hexString) if (normalized.length % 2 !== 0) { throw new Error('hex string must have even length') } diff --git a/web-v2/src/utils/uint.ts b/web-v2/src/utils/uint.ts new file mode 100644 index 0000000..9966ce6 --- /dev/null +++ b/web-v2/src/utils/uint.ts @@ -0,0 +1,15 @@ +const MAX_U8 = 0xff +const MAX_U32 = 0xffff_ffff +const MAX_U64 = (1n << 64n) - 1n + +export function isUint8(value: number): boolean { + return Number.isInteger(value) && value >= 0 && value <= MAX_U8 +} + +export function isUint32(value: number): boolean { + return Number.isInteger(value) && value >= 0 && value <= MAX_U32 +} + +export function isUint64(value: bigint): boolean { + return value >= 0n && value <= MAX_U64 +} diff --git a/web-v2/src/vite-env.d.ts b/web-v2/src/vite-env.d.ts index 0a75125..6074726 100644 --- a/web-v2/src/vite-env.d.ts +++ b/web-v2/src/vite-env.d.ts @@ -14,6 +14,7 @@ declare module 'virtual:simplicity-sources' { lending: string asset_auth: string script_auth: string + issuance_factory: string } export const sources: SimplicitySources From ece3a930f876e3ef4c88f7615c999b3ef8cad637 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Thu, 4 Jun 2026 19:15:06 +0300 Subject: [PATCH 08/16] feat(asset-auth): implement asset authorization program feat(asset-auth-vault): add asset authorization vault program feat(lending): create lending program with offer management --- web-v2/Dockerfile | 2 +- web-v2/simplicity-covenants.config.json | 1 + .../Dashboard/CreateBorrowerAccountDemo.tsx | 1 - .../src/pages/Dashboard/CreateOfferDemo.tsx | 926 ++++++++++++++++++ web-v2/src/pages/Dashboard/index.tsx | 2 + .../simplicity/asset-auth-vault/program.ts | 183 ++++ web-v2/src/simplicity/asset-auth/program.ts | 55 ++ .../simplicity/issuance-factory/program.ts | 21 +- web-v2/src/simplicity/lending/program.ts | 333 +++++++ web-v2/src/utils/uint.ts | 23 + web-v2/src/vite-env.d.ts | 1 + 11 files changed, 1527 insertions(+), 21 deletions(-) create mode 100644 web-v2/src/pages/Dashboard/CreateOfferDemo.tsx create mode 100644 web-v2/src/simplicity/asset-auth-vault/program.ts create mode 100644 web-v2/src/simplicity/asset-auth/program.ts create mode 100644 web-v2/src/simplicity/lending/program.ts diff --git a/web-v2/Dockerfile b/web-v2/Dockerfile index ad9a0ad..fad13d9 100644 --- a/web-v2/Dockerfile +++ b/web-v2/Dockerfile @@ -4,7 +4,7 @@ FROM rust:1.91-bookworm AS lwk-builder ARG LWK_REF=wasm-0.16-expose-external-utxos # ARG LWK_COMMIT=4786e456db6efc05413721f6489899a6426eaa47 -ARG LWK_COMMIT=9f1faea1a6765b7591a8377834be38f4a8f090ae +ARG LWK_COMMIT=c5d422821cf19aa0bf1a11f669a4214b04bde3eb RUN apt-get update && apt-get install -y --no-install-recommends \ git pkg-config libssl-dev ca-certificates clang \ diff --git a/web-v2/simplicity-covenants.config.json b/web-v2/simplicity-covenants.config.json index 5c9c0fe..3828f33 100644 --- a/web-v2/simplicity-covenants.config.json +++ b/web-v2/simplicity-covenants.config.json @@ -2,6 +2,7 @@ "covenants": [ { "id": "lending", "path": "../crates/contracts/simf/lending.simf" }, { "id": "asset_auth", "path": "../crates/contracts/simf/asset_auth.simf" }, + { "id": "asset_auth_vault", "path": "../crates/contracts/simf/asset_auth_vault.simf" }, { "id": "script_auth", "path": "../crates/contracts/simf/script_auth.simf" }, { "id": "issuance_factory", "path": "../crates/contracts/simf/issuance_factory.simf" } ] diff --git a/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx b/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx index 330e448..cdffeb5 100644 --- a/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx +++ b/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx @@ -129,7 +129,6 @@ export default function CreateBorrowerAccountDemo() { const issuanceFactoryProgram = loadIssuanceFactoryProgram({ issuingUtxosCount: ISSUING_UTXOS_COUNT, reissuanceFlags: REISSUANCE_FLAGS, - factoryOwnerPubkey: key.toBytes(), }) const factoryAddress = issuanceFactoryProgram.createP2trAddress(key, lwkNetwork) const factoryAddressString = factoryAddress.toString() diff --git a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx b/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx new file mode 100644 index 0000000..8231a42 --- /dev/null +++ b/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx @@ -0,0 +1,926 @@ +import { + Address, + AssetId, + assetIdFromIssuance, + ContractHash, + ExternalUtxo, + IssuanceRecipient, + type Network, + OutPoint, + Script, + SimplicityLogLevel, + Transaction, + TxBuilder, + TxOutSecrets, + type WalletTxOut, + type XOnlyPublicKey, +} from 'lwk_web' +import { useCallback, useEffect, useMemo, useState } from 'react' + +import { broadcastTx, fetchLatestBlockHeight, fetchTxRaw } from '@/api/esplora/methods' +import { getTxExplorerUrl } from '@/api/esplora/utils' +import { UiButton } from '@/components/ui/UiButton' +import { UiSelect, type UiSelectOption } from '@/components/ui/UiSelect' +import { UiTextField } from '@/components/ui/UiTextField' +import { findUtxoByOutpoint, getPolicyAssetUtxos, outpointToString } from '@/lwk/utxo' +import { useLwk } from '@/providers/lwk/useLwk' +import { useWallet } from '@/providers/wallet/useWallet' +import { + buildIssuanceFactoryWitness, + loadIssuanceFactoryProgram, +} from '@/simplicity/issuance-factory/program' +import { + buildDerivedLendingOfferProgramParams, + buildPendingOfferMetadata, + loadLendingProgram, +} from '@/simplicity/lending/program' +import { useTxConfirmations } from '@/simplicity/script-auth/helpers' +import { loadScriptAuthProgram } from '@/simplicity/script-auth/program' +import { bytesToHex, hexToBytes, isHexStringOfByteLength, normalizeHex } from '@/utils/hex' + +interface CreateOfferForm { + factoryAuthOutpoint: string + issuanceFactoryOutpoint: string + factoryAssetId: string + collateralOutpoint: string + collateralAmount: string + principalAssetId: string + principalAmount: string + principalInterestRate: string + loanDurationBlocks: string + protocolFeeKeeperAssetId: string +} + +interface CreateOfferSummary { + inputs: Record + outputs: Record + assetIds: Record + scripts: Record + offerParameters: Record + metadataOpReturnHex: string +} + +interface BroadcastState { + busy: boolean + error: string | null + summary: CreateOfferSummary | null + txid: string | null +} + +interface WalletUtxosState { + busy: boolean + error: string | null +} + +const TEST_ASSET_ID = '38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5' +const DEFAULT_COLLATERAL_AMOUNT = '3000' +const DEFAULT_PRINCIPAL_AMOUNT = '10000' +const DEFAULT_INTEREST_RATE_BPS = '1000' +const DEFAULT_LOAN_DURATION_BLOCKS = '144' +const ISSUING_UTXOS_COUNT = 2 +const REISSUANCE_FLAGS = 0n +const REISSUANCE_TOKEN_AMOUNT = 0n +const NFT_AMOUNT = 1n +const DEFAULT_FEE_RATE = 100 +const DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY = 30_000 + +const INITIAL_STATE: BroadcastState = { + busy: false, + error: null, + summary: null, + txid: null, +} + +const EMPTY_FORM: CreateOfferForm = { + factoryAuthOutpoint: '5ecc77af31963bfef85418a2b196fc274626b1e0094eb280d37a908f0171a13a:0', + issuanceFactoryOutpoint: '5ecc77af31963bfef85418a2b196fc274626b1e0094eb280d37a908f0171a13a:1', + factoryAssetId: 'a61ab9c860e382039cb5df9386319887c1a3e60116f5fcb7ad3497b430806d18', + collateralOutpoint: '', + collateralAmount: DEFAULT_COLLATERAL_AMOUNT, + principalAssetId: TEST_ASSET_ID, + principalAmount: DEFAULT_PRINCIPAL_AMOUNT, + principalInterestRate: DEFAULT_INTEREST_RATE_BPS, + loanDurationBlocks: DEFAULT_LOAN_DURATION_BLOCKS, + protocolFeeKeeperAssetId: TEST_ASSET_ID, +} + +export default function CreateOfferDemo() { + const { lwkNetwork } = useLwk() + const { + connectionStatus, + getReceiveAddress, + getWalletUtxos, + getWollet, + getXOnlyPublicKey, + signPset, + syncWallet, + } = useWallet() + const [form, setForm] = useState(EMPTY_FORM) + const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) + const [state, setState] = useState({ ...INITIAL_STATE }) + const [walletUtxos, setWalletUtxos] = useState([]) + const [walletUtxosState, setWalletUtxosState] = useState({ + busy: false, + error: null, + }) + const confirmations = useTxConfirmations(state.txid) + + const policyAssetId = useMemo(() => lwkNetwork.policyAsset().toString(), [lwkNetwork]) + const collateralUtxoOptions = useMemo( + () => getPolicyAssetUtxos(walletUtxos, policyAssetId).map(formatCollateralUtxoOption), + [policyAssetId, walletUtxos], + ) + + const refreshWalletUtxos = useCallback(async () => { + if (connectionStatus !== 'ready') { + setWalletUtxos([]) + setWalletUtxosState({ busy: false, error: null }) + return + } + + setWalletUtxosState({ busy: true, error: null }) + try { + await syncWallet() + setWalletUtxos(await getWalletUtxos()) + setWalletUtxosState({ busy: false, error: null }) + } catch (err) { + setWalletUtxosState({ + busy: false, + error: err instanceof Error ? err.message : String(err), + }) + } + }, [connectionStatus, getWalletUtxos, syncWallet]) + + useEffect(() => { + void refreshWalletUtxos() + }, [refreshWalletUtxos]) + + const setField = (field: keyof CreateOfferForm, value: string) => { + setForm(current => ({ ...current, [field]: value })) + } + + const createOffer = async () => { + setState(current => ({ ...current, busy: true, error: null, summary: null, txid: null })) + + let createStage = 'initializing' + try { + createStage = 'get x-only public key' + const key = await getXOnlyPublicKey() + if (!key) { + throw new Error('Missing x-only public key') + } + setXOnlyPublicKey(key) + + createStage = 'get receive address' + const receiveAddressString = await getReceiveAddress() + if (!receiveAddressString) { + throw new Error('Missing receive address') + } + + createStage = 'get wollet' + const wollet = await getWollet() + if (!wollet) { + throw new Error('Wallet not connected') + } + + createStage = 'sync wallet and load UTXOs' + await syncWallet() + const walletUtxos = await getWalletUtxos() + setWalletUtxos(walletUtxos) + logWalletUtxoLookup(walletUtxos, [ + { label: 'FactoryAuth', outpoint: form.factoryAuthOutpoint }, + { label: 'Collateral', outpoint: form.collateralOutpoint }, + ]) + const factoryAuthUtxo = findUtxoByOutpoint(walletUtxos, form.factoryAuthOutpoint.trim()) + const collateralUtxo = requireWalletUtxo(walletUtxos, form.collateralOutpoint, 'Collateral') + + createStage = 'parse and validate form' + const factoryAsset = parseAssetId(form.factoryAssetId, 'Factory asset id') + const principalAsset = parseAssetId(form.principalAssetId, 'Principal asset id') + const protocolFeeKeeperAsset = parseAssetId( + form.protocolFeeKeeperAssetId, + 'Protocol fee keeper asset id', + ) + const policyAsset = lwkNetwork.policyAsset() + const factoryAssetString = factoryAsset.toString() + const principalAssetString = principalAsset.toString() + const protocolFeeKeeperAssetString = protocolFeeKeeperAsset.toString() + const policyAssetString = policyAsset.toString() + + if (factoryAuthUtxo) { + assertWalletUtxoAssetAndAmount( + factoryAuthUtxo, + factoryAssetString, + NFT_AMOUNT, + 'FactoryAuth', + ) + } else { + console.info('[CreateOfferDemo] FactoryAuth not in wallet UTXO set; using external UTXO', { + outpoint: form.factoryAuthOutpoint.trim(), + asset: factoryAssetString, + amount: NFT_AMOUNT.toString(), + }) + } + assertWalletUtxoAssetAndAmount( + collateralUtxo, + policyAssetString, + parseBigint(form.collateralAmount, 'Collateral amount'), + 'Collateral', + ) + + const factoryAuthOutpointString = form.factoryAuthOutpoint.trim() + const issuanceFactoryOutpointString = form.issuanceFactoryOutpoint.trim() + const collateralOutpointString = form.collateralOutpoint.trim() + const issuanceFactoryOutpoint = parseOutPoint(issuanceFactoryOutpointString) + const collateralOutpoint = parseOutPoint(collateralOutpointString) + + createStage = 'prepare addresses and IssuanceFactory external UTXO' + const receiveAddressExplicitString = Address.parse(receiveAddressString, lwkNetwork) + .toUnconfidential() + .toString() + const issuanceFactoryProgram = loadIssuanceFactoryProgram({ + issuingUtxosCount: ISSUING_UTXOS_COUNT, + reissuanceFlags: REISSUANCE_FLAGS, + }) + const issuanceFactoryAddress = issuanceFactoryProgram.createP2trAddress(key, lwkNetwork) + const issuanceFactoryAddressString = issuanceFactoryAddress.toString() + const issuanceFactoryScriptPubkeyHex = bytesToHex( + issuanceFactoryAddress.scriptPubkey().bytes(), + ) + + const issuanceFactoryTx = Transaction.fromBytes( + await fetchTxRaw(issuanceFactoryOutpoint.txid().toString()), + ) + const issuanceFactoryTxOut = issuanceFactoryTx.outputs[issuanceFactoryOutpoint.vout()] + if (!issuanceFactoryTxOut) { + throw new Error('IssuanceFactory transaction does not have the selected output') + } + + const issuanceFactoryExternalUtxo = new ExternalUtxo( + issuanceFactoryOutpoint.vout(), + issuanceFactoryTx, + TxOutSecrets.fromExplicit(parseAssetId(factoryAssetString, 'Factory asset id'), NFT_AMOUNT), + DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, + true, + ) + const factoryAuthExternalUtxo = factoryAuthUtxo + ? null + : await buildExplicitExternalUtxo( + factoryAuthOutpointString, + factoryAssetString, + NFT_AMOUNT, + 'FactoryAuth', + ) + + const borrowerNftAsset = assetIdFromIssuance(issuanceFactoryOutpoint, emptyContractHash()) + const lenderNftAsset = assetIdFromIssuance(collateralOutpoint, emptyContractHash()) + const borrowerNftAssetString = borrowerNftAsset.toString() + const lenderNftAssetString = lenderNftAsset.toString() + const currentBlockHeight = await fetchLatestBlockHeight() + const loanDurationBlocks = parseNumber(form.loanDurationBlocks, 'Loan duration blocks') + const offerParameters = { + collateralAmount: parseBigint(form.collateralAmount, 'Collateral amount'), + principalAmount: parseBigint(form.principalAmount, 'Principal amount'), + principalInterestRate: parseNumber(form.principalInterestRate, 'Interest rate bps'), + loanExpirationTime: currentBlockHeight + loanDurationBlocks, + } + + createStage = 'compile lending and ScriptAuth programs' + const derivedLendingParams = buildDerivedLendingOfferProgramParams( + { + collateralAssetId: parseAssetId(policyAssetString, 'Policy asset id').toBytes(), + principalAssetId: parseAssetId(principalAssetString, 'Principal asset id').toBytes(), + borrowerNftAssetId: parseAssetId( + borrowerNftAssetString, + 'Borrower NFT asset id', + ).toBytes(), + lenderNftAssetId: parseAssetId(lenderNftAssetString, 'Lender NFT asset id').toBytes(), + protocolFeeKeeperAssetId: parseAssetId( + protocolFeeKeeperAssetString, + 'Protocol fee keeper asset id', + ).toBytes(), + offerParameters, + }, + key, + lwkNetwork, + ) + const lendingProgram = loadLendingProgram(derivedLendingParams) + const lendingAddress = lendingProgram.createP2trAddress(key, lwkNetwork) + const lendingAddressString = lendingAddress.toString() + const lendingScriptPubkeyHex = bytesToHex(lendingAddress.scriptPubkey().bytes()) + const lendingScriptHash = hexToBytes(new Script(lendingScriptPubkeyHex).jet_sha256_hex()) + const lenderNftScriptAuthProgram = loadScriptAuthProgram(lendingScriptHash) + const lenderNftScriptAuthAddress = lenderNftScriptAuthProgram.createP2trAddress( + key, + lwkNetwork, + ) + const lenderNftScriptAuthAddressString = lenderNftScriptAuthAddress.toString() + const metadata = await buildPendingOfferMetadata({ + principalAssetId: parseAssetId(principalAssetString, 'Principal asset id').toBytes(), + offerParameters, + }) + + logAddressShape('receive explicit', receiveAddressExplicitString) + logAddressShape('receive confidential', receiveAddressString) + logAddressShape('issuanceFactory covenant', issuanceFactoryAddressString) + logAddressShape('lender NFT ScriptAuth', lenderNftScriptAuthAddressString) + + createStage = 'TxBuilder.new' + console.info('[CreateOfferDemo] stage', createStage) + let txBuilder = new TxBuilder(lwkNetwork) + + createStage = 'TxBuilder.feeRate' + console.info('[CreateOfferDemo] stage', createStage, { feeRate: DEFAULT_FEE_RATE }) + txBuilder = txBuilder.feeRate(DEFAULT_FEE_RATE) + + createStage = 'TxBuilder.setInputOrder' + console.info('[CreateOfferDemo] stage', createStage, { + inputOrder: [ + factoryAuthOutpointString, + issuanceFactoryOutpointString, + collateralOutpointString, + ], + }) + txBuilder = txBuilder.setInputOrder([ + parseOutPoint(factoryAuthOutpointString), + parseOutPoint(issuanceFactoryOutpointString), + parseOutPoint(collateralOutpointString), + ]) + + const externalUtxos = factoryAuthExternalUtxo + ? [factoryAuthExternalUtxo, issuanceFactoryExternalUtxo] + : [issuanceFactoryExternalUtxo] + + createStage = 'TxBuilder.addExternalUtxos covenant/explicit inputs' + console.info('[CreateOfferDemo] stage', createStage, { + externalOutpoints: factoryAuthExternalUtxo + ? [factoryAuthOutpointString, issuanceFactoryOutpointString] + : [issuanceFactoryOutpointString], + }) + txBuilder = txBuilder.addExternalUtxos(externalUtxos) + + createStage = 'TxBuilder.addExplicitRecipient FactoryAuth back to user' + console.info('[CreateOfferDemo] stage', createStage, { + address: receiveAddressExplicitString, + asset: factoryAssetString, + amount: NFT_AMOUNT.toString(), + }) + txBuilder = txBuilder.addExplicitRecipient( + parseAddress(receiveAddressExplicitString, lwkNetwork), + NFT_AMOUNT, + parseAssetId(factoryAssetString, 'Factory asset id'), + ) + + createStage = 'TxBuilder.addExplicitScriptOutput IssuanceFactory covenant' + console.info('[CreateOfferDemo] stage', createStage, { + scriptPubkeyHex: issuanceFactoryScriptPubkeyHex, + asset: factoryAssetString, + amount: NFT_AMOUNT.toString(), + }) + txBuilder = txBuilder.addExplicitScriptOutput( + new Script(issuanceFactoryScriptPubkeyHex), + NFT_AMOUNT, + parseAssetId(factoryAssetString, 'Factory asset id'), + ) + + createStage = 'TxBuilder.issueAssetToRecipients Borrower NFT to user' + console.info('[CreateOfferDemo] stage', createStage, { + inputOutpoint: issuanceFactoryOutpointString, + address: receiveAddressExplicitString, + amount: NFT_AMOUNT.toString(), + }) + txBuilder = txBuilder.issueAssetToRecipients( + [ + IssuanceRecipient.fromAddress( + NFT_AMOUNT, + parseAddress(receiveAddressExplicitString, lwkNetwork), + ), + ], + REISSUANCE_TOKEN_AMOUNT, + null, + null, + parseOutPoint(issuanceFactoryOutpointString), + ) + + createStage = 'TxBuilder.issueAssetToRecipients Lender NFT to ScriptAuth' + console.info('[CreateOfferDemo] stage', createStage, { + inputOutpoint: collateralOutpointString, + address: lenderNftScriptAuthAddressString, + amount: NFT_AMOUNT.toString(), + }) + txBuilder = txBuilder.issueAssetToRecipients( + [ + IssuanceRecipient.fromAddress( + NFT_AMOUNT, + parseAddress(lenderNftScriptAuthAddressString, lwkNetwork), + ), + ], + REISSUANCE_TOKEN_AMOUNT, + null, + null, + parseOutPoint(collateralOutpointString), + ) + + createStage = 'TxBuilder.addExplicitScriptOutput metadata OP_RETURN' + console.info('[CreateOfferDemo] stage', createStage, { + asset: policyAssetString, + metadataHex: bytesToHex(metadata), + }) + txBuilder = txBuilder.addExplicitScriptOutput( + Script.newOpReturn(metadata), + 0n, + parseAssetId(policyAssetString, 'Policy asset id'), + ) + + createStage = 'TxBuilder.addExplicitScriptOutput Lending covenant collateral' + console.info('[CreateOfferDemo] stage', createStage, { + scriptPubkeyHex: lendingScriptPubkeyHex, + asset: policyAssetString, + amount: offerParameters.collateralAmount.toString(), + }) + txBuilder = txBuilder.addExplicitScriptOutput( + new Script(lendingScriptPubkeyHex), + offerParameters.collateralAmount, + parseAssetId(policyAssetString, 'Policy asset id'), + ) + + createStage = 'TxBuilder.finish' + console.info('[CreateOfferDemo] stage', createStage) + const pset = txBuilder.finish(wollet) + + console.log( + pset.outputs().map((output, index) => ({ + index, + asset: output.asset()?.toString?.(), + amount: output.amount()?.toString?.(), + })), + ) + + createStage = 'sign offer PSET' + const signedPset = await signPset(pset) + createStage = 'finalize wallet inputs' + console.log('[CreateOfferDemo] finalize params', { + currentIndex: 1, + prevOutputs: [ + factoryAuthOutpointString, + issuanceFactoryOutpointString, + collateralOutpointString, + ], + }) + const finalizedWalletPset = wollet.finalize(signedPset) + const txWithWalletWitnesses = finalizedWalletPset.extractTx() + + console.log( + txWithWalletWitnesses.outputs.map((output, index) => ({ + index, + script: bytesToHex(output.scriptPubkey().bytes()), + isOpReturn: bytesToHex(output.scriptPubkey().bytes()).startsWith('6a'), + })), + ) + + console.table( + txWithWalletWitnesses.outputs.map((output, index) => ({ + index, + scriptHash: output.scriptPubkey().jet_sha256_hex(), + script: bytesToHex(output.scriptPubkey().bytes()), + })), + ) + + console.table( + txWithWalletWitnesses.inputs.map((input, index) => ({ + index, + outpoint: input.outpoint().toString(), + })), + ) + + console.log(txWithWalletWitnesses.inputs) + + console.table( + txWithWalletWitnesses.inputs.map((input, index) => ({ + index, + outpoint: input.outpoint().toString(), + })), + ) + + console.log(txWithWalletWitnesses.toString()) + + createStage = 'load previous txouts for Simplicity finalize' + const finalizationFactoryAuthOutpoint = parseOutPoint(factoryAuthOutpointString) + const finalizationCollateralOutpoint = parseOutPoint(collateralOutpointString) + const factoryAuthTx = Transaction.fromBytes( + await fetchTxRaw(finalizationFactoryAuthOutpoint.txid().toString()), + ) + const factoryAuthTxOut = factoryAuthTx.outputs[finalizationFactoryAuthOutpoint.vout()] + if (!factoryAuthTxOut) { + throw new Error('FactoryAuth transaction does not have the selected output') + } + + const collateralTx = Transaction.fromBytes( + await fetchTxRaw(finalizationCollateralOutpoint.txid().toString()), + ) + const collateralTxOut = collateralTx.outputs[finalizationCollateralOutpoint.vout()] + if (!collateralTxOut) { + throw new Error('Collateral transaction does not have the selected output') + } + + createStage = 'finalize IssuanceFactory covenant input' + + console.log('factory txout script hash', issuanceFactoryTxOut.scriptPubkey().jet_sha256_hex()) + + console.log( + 'output 4 script hash', + txWithWalletWitnesses.outputs[4]?.scriptPubkey().jet_sha256_hex(), + ) + + console.log('EXPECTED', { + factoryAssetId: factoryAssetString, + borrowerNftAssetId: borrowerNftAssetString, + lenderNftAssetId: lenderNftAssetString, + }) + + const finalizedTx = issuanceFactoryProgram.finalizeTransaction( + txWithWalletWitnesses, + key, + [factoryAuthTxOut, issuanceFactoryTxOut, collateralTxOut], + 1, + buildIssuanceFactoryWitness({ + branch: 'IssueAssets', + outputIndex: 0, + }), + lwkNetwork, + SimplicityLogLevel.Trace, + ) + + const witness = buildIssuanceFactoryWitness({ + branch: 'IssueAssets', + outputIndex: 0, + }) + + console.log('ISSUANCE FACTORY SCRIPT', issuanceFactoryScriptPubkeyHex) + console.log('SCRIPT AUTH SCRIPT', lenderNftScriptAuthAddress.scriptPubkey().toString()) + console.log('LENDING SCRIPT', lendingScriptPubkeyHex) + + console.log('[CreateOfferDemo] IssuanceFactory witness', witness) + + createStage = 'broadcast transaction' + const txid = await broadcastTx(finalizedTx.toString()) + + setState({ + busy: false, + error: null, + summary: { + inputs: { + '0 FactoryAuth': form.factoryAuthOutpoint, + '1 IssuanceFactory covenant': form.issuanceFactoryOutpoint, + '2 Collateral LBTC': form.collateralOutpoint, + }, + outputs: { + '0 FactoryAuth back to user': receiveAddressExplicitString, + '1 IssuanceFactory back to covenant': issuanceFactoryAddressString, + '2 Borrower NFT to user': receiveAddressExplicitString, + '3 Lender NFT to ScriptAuth': lenderNftScriptAuthAddressString, + '4 Metadata OP_RETURN': bytesToHex(Script.newOpReturn(metadata).bytes()), + '5 Lending covenant': lendingAddressString, + }, + assetIds: { + factoryAssetId: factoryAssetString, + collateralAssetId: policyAssetString, + principalAssetId: principalAssetString, + borrowerNftAssetId: borrowerNftAssetString, + lenderNftAssetId: lenderNftAssetString, + protocolFeeKeeperAssetId: protocolFeeKeeperAssetString, + }, + scripts: { + lendingScriptHash: bytesToHex(lendingScriptHash), + lenderVaultCovHash: bytesToHex(derivedLendingParams.lenderVaultCovHash), + finalizedLenderVaultCovHash: bytesToHex( + derivedLendingParams.finalizedLenderVaultCovHash, + ), + protocolFeeVaultCovHash: bytesToHex(derivedLendingParams.protocolFeeVaultCovHash), + finalizedProtocolFeeVaultCovHash: bytesToHex( + derivedLendingParams.finalizedProtocolFeeVaultCovHash, + ), + principalOutputScriptHash: bytesToHex(derivedLendingParams.principalOutputScriptHash), + }, + offerParameters: { + collateralAmount: offerParameters.collateralAmount.toString(), + principalAmount: offerParameters.principalAmount.toString(), + principalInterestRate: offerParameters.principalInterestRate.toString(), + currentBlockHeight: currentBlockHeight.toString(), + loanDurationBlocks: loanDurationBlocks.toString(), + loanExpirationTime: offerParameters.loanExpirationTime.toString(), + }, + metadataOpReturnHex: bytesToHex(Script.newOpReturn(metadata).bytes()), + }, + txid, + }) + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err) + console.error('[CreateOfferDemo] create failed', { createStage, err }) + setState(current => ({ + ...current, + busy: false, + error: `${createStage}: ${errorMessage}`, + })) + } + } + + return ( +
+
Create Offer Demo
+

+ Builds one offer creation transaction: FactoryAuth input, IssuanceFactory covenant input, + and LBTC collateral input. Borrower account UTXOs are entered manually. +

+ +
+ setField('factoryAuthOutpoint', value)} + description='Manual fallback for explicit wallet UTXOs that LWK scan does not list' + /> + setField('issuanceFactoryOutpoint', value)} + /> + setField('factoryAssetId', value)} + /> + setField('collateralOutpoint', key ? String(key) : '')} + description={ + collateralUtxoOptions.length + ? `${collateralUtxoOptions.length} wallet LBTC UTXO(s)` + : 'No wallet LBTC UTXOs loaded' + } + /> + setField('collateralAmount', value)} + /> + setField('principalAmount', value)} + /> + setField('principalAssetId', value)} + /> + setField('protocolFeeKeeperAssetId', value)} + /> + setField('principalInterestRate', value)} + /> + setField('loanDurationBlocks', value)} + /> +
+ +
+ Collateral asset is wallet policy asset: {policyAssetId} +
+ {walletUtxosState.error ? ( +

Wallet UTXOs: {walletUtxosState.error}

+ ) : null} + +
+ + Refresh LBTC UTXOs + + + Create Offer + +
+ + {state.error ?

Create: {state.error}

: null} + + + +
+        {JSON.stringify(
+          {
+            connectionStatus,
+            hasPubkey: !!xOnlyPublicKey,
+            broadcasting: state.busy,
+            txid: state.txid,
+            confirmations,
+            error: state.error,
+          },
+          null,
+          2,
+        )}
+      
+
+ ) +} + +function BroadcastResult({ + confirmations, + summary, + txid, +}: { + confirmations: number | null + summary: CreateOfferSummary | null + txid: string | null +}) { + if (!txid) { + return null + } + + return ( +
+
Offer Created
+ + {txid} + +

+ {confirmations !== null + ? `${confirmations} confirmation${confirmations === 1 ? '' : 's'}` + : 'Waiting for confirmation...'} +

+ {summary ? ( +
+          {JSON.stringify(summary, null, 2)}
+        
+ ) : null} +
+ ) +} + +function formatCollateralUtxoOption(utxo: WalletTxOut): UiSelectOption { + const outpoint = outpointToString(utxo) + const height = utxo.height() + const status = height === undefined ? 'mempool' : `height ${height}` + return { + id: outpoint, + label: `${outpoint} | ${utxo.unblinded().value().toString()} sats | ${status}`, + } +} + +function logWalletUtxoLookup( + walletUtxos: WalletTxOut[], + targets: { label: string; outpoint: string }[], +) { + console.group('[CreateOfferDemo] wallet UTXO lookup') + console.table( + targets.map(target => ({ + label: target.label, + rawOutpoint: target.outpoint, + trimmedOutpoint: target.outpoint.trim(), + existsInWallet: Boolean(findUtxoByOutpoint(walletUtxos, target.outpoint.trim())), + })), + ) + console.table( + walletUtxos.map(utxo => ({ + outpoint: outpointToString(utxo), + asset: utxo.unblinded().asset().toString(), + amount: utxo.unblinded().value().toString(), + height: utxo.height() ?? 'mempool', + address: utxo.address().toString(), + })), + ) + console.groupEnd() +} + +function logAddressShape(label: string, addressString: string) { + try { + const address = new Address(addressString) + console.info('[CreateOfferDemo] address shape', { + label, + address: addressString, + isBlinded: address.isBlinded(), + isMainnet: address.isMainnet(), + }) + } catch (err) { + console.info('[CreateOfferDemo] address shape parse failed', { + label, + address: addressString, + err, + }) + } +} + +function parseOutPoint(value: string): OutPoint { + const trimmed = value.trim() + if (!/^[0-9a-fA-F]{64}:\d+$/.test(trimmed)) { + throw new Error(`Invalid outpoint: ${value}`) + } + return new OutPoint(trimmed) +} + +function parseAssetId(value: string, label: string): AssetId { + const normalized = normalizeHex(value) + if (!isHexStringOfByteLength(normalized, 32)) { + throw new Error(`${label} must be a 32-byte hex asset id`) + } + return AssetId.fromString(normalized) +} + +function parseAddress(value: string, network: Network): Address { + // Address.parse currently rejects explicit tex1... addresses in lwk_wasm. + // TxBuilder still needs explicit addresses for addExplicitRecipient, so use + // the constructor here and keep network-specific validation at call sites. + void network + return new Address(value) +} + +async function buildExplicitExternalUtxo( + outpointString: string, + assetIdString: string, + amount: bigint, + label: string, +): Promise { + const outpoint = parseOutPoint(outpointString) + const tx = Transaction.fromBytes(await fetchTxRaw(outpoint.txid().toString())) + const txOut = tx.outputs[outpoint.vout()] + if (!txOut) { + throw new Error(`${label} transaction does not have the selected output`) + } + + return new ExternalUtxo( + outpoint.vout(), + tx, + TxOutSecrets.fromExplicit(parseAssetId(assetIdString, `${label} asset id`), amount), + DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, + true, + ) +} + +function parseBigint(value: string, label: string): bigint { + const trimmed = value.trim() + if (!/^\d+$/.test(trimmed)) { + throw new Error(`${label} must be an integer`) + } + return BigInt(trimmed) +} + +function parseNumber(value: string, label: string): number { + const trimmed = value.trim() + if (!/^\d+$/.test(trimmed)) { + throw new Error(`${label} must be an integer`) + } + return Number.parseInt(trimmed, 10) +} + +function requireWalletUtxo( + walletUtxos: WalletTxOut[], + outpoint: string, + label: string, +): WalletTxOut { + const utxo = findUtxoByOutpoint(walletUtxos, outpoint.trim()) + if (!utxo) { + throw new Error(`${label} wallet UTXO not found`) + } + return utxo +} + +function assertWalletUtxoAssetAndAmount( + utxo: WalletTxOut, + assetId: string, + minAmount: bigint, + label: string, +) { + const unblinded = utxo.unblinded() + if (unblinded.asset().toString() !== assetId) { + throw new Error(`${label} UTXO has unexpected asset ${unblinded.asset().toString()}`) + } + if (unblinded.value() < minAmount) { + throw new Error(`${label} UTXO amount is lower than ${minAmount.toString()}`) + } +} + +function emptyContractHash(): ContractHash { + return ContractHash.fromBytes(new Uint8Array(32)) +} diff --git a/web-v2/src/pages/Dashboard/index.tsx b/web-v2/src/pages/Dashboard/index.tsx index 6958481..d4787e8 100644 --- a/web-v2/src/pages/Dashboard/index.tsx +++ b/web-v2/src/pages/Dashboard/index.tsx @@ -1,4 +1,5 @@ import CreateBorrowerAccountDemo from './CreateBorrowerAccountDemo' +import CreateOfferDemo from './CreateOfferDemo' import ScriptAuthCovenantDemo from './ScriptAuthCovenantDemo' import { WalletDemo } from './WalletDemo' @@ -8,6 +9,7 @@ export default function DashboardPage() {

Dashboard

+
) diff --git a/web-v2/src/simplicity/asset-auth-vault/program.ts b/web-v2/src/simplicity/asset-auth-vault/program.ts new file mode 100644 index 0000000..d1581cb --- /dev/null +++ b/web-v2/src/simplicity/asset-auth-vault/program.ts @@ -0,0 +1,183 @@ +import { + SimplicityArguments, + SimplicityProgram, + SimplicityType, + SimplicityTypedValue, + SimplicityWitnessValues, +} from 'lwk_web' +import { sources } from 'virtual:simplicity-sources' + +import { bytes32ToHex } from '@/utils/hex' +import { assertBytes32, assertUint32, assertUint64 } from '@/utils/uint' + +const ARGUMENTS = { + VAULT_ASSET_ID: 'VAULT_ASSET_ID', + KEEPER_AUTH_ASSET_ID: 'KEEPER_AUTH_ASSET_ID', + SUPPLIER_AUTH_ASSET_ID: 'SUPPLIER_AUTH_ASSET_ID', + KEEPER_AUTH_ASSET_AMOUNT: 'KEEPER_AUTH_ASSET_AMOUNT', + FINALIZED_VAULT_COV_HASH: 'FINALIZED_VAULT_COV_HASH', + IS_ACTIVE: 'IS_ACTIVE', + WITH_KEEPER_ASSET_BURN: 'WITH_KEEPER_ASSET_BURN', + WITH_SUPPLIER_ASSET_BURN: 'WITH_SUPPLIER_ASSET_BURN', +} as const + +const WITNESS = { + PATH: 'PATH', +} as const + +export interface AssetAuthVaultProgramParams { + vaultAssetId: Uint8Array + keeperAuthAssetId: Uint8Array + supplierAuthAssetId: Uint8Array + keeperAuthAssetAmount: bigint + finalizedVaultCovHash: Uint8Array + isActive: boolean + withKeeperAssetBurn: boolean + withSupplierAssetBurn: boolean +} + +export type AssetAuthVaultWitnessParams = + | { + branch: 'WithdrawAll' + inputKeeperIndex: number + outputKeeperIndex: number + } + | { + branch: 'WithdrawPart' + inputKeeperIndex: number + outputKeeperIndex: number + vaultOutputIndex: number + amountToWithdraw: bigint + } + | { + branch: 'Supply' + inputSupplierIndex: number + outputSupplierIndex: number + vaultOutputIndex: number + amountToSupply: bigint + } + | { + branch: 'FinalSupply' + inputSupplierIndex: number + outputSupplierIndex: number + finalizedVaultOutputIndex: number + amountToSupply: bigint + } + +export function loadAssetAuthVaultProgram(params: AssetAuthVaultProgramParams): SimplicityProgram { + return SimplicityProgram.load(sources.asset_auth_vault, buildAssetAuthVaultArguments(params)) +} + +export function buildAssetAuthVaultArguments( + params: AssetAuthVaultProgramParams, +): SimplicityArguments { + const { + finalizedVaultCovHash, + isActive, + keeperAuthAssetAmount, + keeperAuthAssetId, + supplierAuthAssetId, + vaultAssetId, + withKeeperAssetBurn, + withSupplierAssetBurn, + } = params + + assertBytes32(vaultAssetId, 'vaultAssetId') + assertBytes32(keeperAuthAssetId, 'keeperAuthAssetId') + assertBytes32(supplierAuthAssetId, 'supplierAuthAssetId') + assertBytes32(finalizedVaultCovHash, 'finalizedVaultCovHash') + + assertUint64(keeperAuthAssetAmount, 'keeperAuthAssetAmount') + + return new SimplicityArguments() + .addValue( + ARGUMENTS.VAULT_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(vaultAssetId)), + ) + .addValue( + ARGUMENTS.KEEPER_AUTH_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(keeperAuthAssetId)), + ) + .addValue( + ARGUMENTS.SUPPLIER_AUTH_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(supplierAuthAssetId)), + ) + .addValue( + ARGUMENTS.KEEPER_AUTH_ASSET_AMOUNT, + SimplicityTypedValue.fromU64(keeperAuthAssetAmount), + ) + .addValue( + ARGUMENTS.FINALIZED_VAULT_COV_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(finalizedVaultCovHash)), + ) + .addValue(ARGUMENTS.IS_ACTIVE, SimplicityTypedValue.fromBoolean(isActive)) + .addValue( + ARGUMENTS.WITH_KEEPER_ASSET_BURN, + SimplicityTypedValue.fromBoolean(withKeeperAssetBurn), + ) + .addValue( + ARGUMENTS.WITH_SUPPLIER_ASSET_BURN, + SimplicityTypedValue.fromBoolean(withSupplierAssetBurn), + ) +} + +export function buildFinalizedAssetAuthVaultParams( + params: Omit, +): AssetAuthVaultProgramParams { + return { + ...params, + finalizedVaultCovHash: new Uint8Array(32), + isActive: false, + } +} + +export function buildActiveAssetAuthVaultParams( + finalizedParams: AssetAuthVaultProgramParams, + finalizedVaultCovHash: Uint8Array, +): AssetAuthVaultProgramParams { + return { + ...finalizedParams, + finalizedVaultCovHash, + isActive: true, + } +} + +export function buildAssetAuthVaultWitness( + params: AssetAuthVaultWitnessParams, +): SimplicityWitnessValues { + const pathType = SimplicityType.fromString( + 'Either, Either<(u32, u32, u32, u64), (u32, u32, u32, u64)>>', + ) + + return new SimplicityWitnessValues().addValue( + WITNESS.PATH, + SimplicityTypedValue.parse(buildAssetAuthVaultPathExpression(params), pathType), + ) +} + +function buildAssetAuthVaultPathExpression(params: AssetAuthVaultWitnessParams): string { + switch (params.branch) { + case 'WithdrawAll': + assertUint32(params.inputKeeperIndex, 'inputKeeperIndex') + assertUint32(params.outputKeeperIndex, 'outputKeeperIndex') + return `Left(Left((${params.inputKeeperIndex}, ${params.outputKeeperIndex})))` + case 'WithdrawPart': + assertUint32(params.inputKeeperIndex, 'inputKeeperIndex') + assertUint32(params.outputKeeperIndex, 'outputKeeperIndex') + assertUint32(params.vaultOutputIndex, 'vaultOutputIndex') + assertUint64(params.amountToWithdraw, 'amountToWithdraw') + return `Left(Right((${params.inputKeeperIndex}, ${params.outputKeeperIndex}, ${params.vaultOutputIndex}, ${params.amountToWithdraw})))` + case 'Supply': + assertUint32(params.inputSupplierIndex, 'inputSupplierIndex') + assertUint32(params.outputSupplierIndex, 'outputSupplierIndex') + assertUint32(params.vaultOutputIndex, 'vaultOutputIndex') + assertUint64(params.amountToSupply, 'amountToSupply') + return `Right(Left((${params.inputSupplierIndex}, ${params.outputSupplierIndex}, ${params.vaultOutputIndex}, ${params.amountToSupply})))` + case 'FinalSupply': + assertUint32(params.inputSupplierIndex, 'inputSupplierIndex') + assertUint32(params.outputSupplierIndex, 'outputSupplierIndex') + assertUint32(params.finalizedVaultOutputIndex, 'finalizedVaultOutputIndex') + assertUint64(params.amountToSupply, 'amountToSupply') + return `Right(Right((${params.inputSupplierIndex}, ${params.outputSupplierIndex}, ${params.finalizedVaultOutputIndex}, ${params.amountToSupply})))` + } +} diff --git a/web-v2/src/simplicity/asset-auth/program.ts b/web-v2/src/simplicity/asset-auth/program.ts new file mode 100644 index 0000000..81e5fab --- /dev/null +++ b/web-v2/src/simplicity/asset-auth/program.ts @@ -0,0 +1,55 @@ +import { + SimplicityArguments, + SimplicityProgram, + SimplicityTypedValue, + SimplicityWitnessValues, +} from 'lwk_web' +import { sources } from 'virtual:simplicity-sources' + +import { bytes32ToHex } from '@/utils/hex' +import { assertBytes32, assertUint32, assertUint64 } from '@/utils/uint' + +const ARGUMENTS = { + ASSET_ID: 'ASSET_ID', + ASSET_AMOUNT: 'ASSET_AMOUNT', + WITH_ASSET_BURN: 'WITH_ASSET_BURN', +} as const + +const WITNESS = { + INPUT_ASSET_INDEX: 'INPUT_ASSET_INDEX', + OUTPUT_ASSET_INDEX: 'OUTPUT_ASSET_INDEX', +} as const + +export interface AssetAuthProgramParams { + assetId: Uint8Array + assetAmount: bigint + withAssetBurn: boolean +} + +export interface AssetAuthWitnessParams { + inputAssetIndex: number + outputAssetIndex: number +} + +export function loadAssetAuthProgram(params: AssetAuthProgramParams): SimplicityProgram { + return SimplicityProgram.load(sources.asset_auth, buildAssetAuthArguments(params)) +} + +export function buildAssetAuthArguments(params: AssetAuthProgramParams): SimplicityArguments { + assertBytes32(params.assetId, 'assetId') + assertUint64(params.assetAmount, 'assetAmount') + + return new SimplicityArguments() + .addValue(ARGUMENTS.ASSET_ID, SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.assetId))) + .addValue(ARGUMENTS.ASSET_AMOUNT, SimplicityTypedValue.fromU64(params.assetAmount)) + .addValue(ARGUMENTS.WITH_ASSET_BURN, SimplicityTypedValue.fromBoolean(params.withAssetBurn)) +} + +export function buildAssetAuthWitness(params: AssetAuthWitnessParams): SimplicityWitnessValues { + assertUint32(params.inputAssetIndex, 'inputAssetIndex') + assertUint32(params.outputAssetIndex, 'outputAssetIndex') + + return new SimplicityWitnessValues() + .addValue(WITNESS.INPUT_ASSET_INDEX, SimplicityTypedValue.fromU32(params.inputAssetIndex)) + .addValue(WITNESS.OUTPUT_ASSET_INDEX, SimplicityTypedValue.fromU32(params.outputAssetIndex)) +} diff --git a/web-v2/src/simplicity/issuance-factory/program.ts b/web-v2/src/simplicity/issuance-factory/program.ts index f304301..dd68c24 100644 --- a/web-v2/src/simplicity/issuance-factory/program.ts +++ b/web-v2/src/simplicity/issuance-factory/program.ts @@ -7,13 +7,11 @@ import { } from 'lwk_web' import { sources } from 'virtual:simplicity-sources' -import { bytes32ToHex, isHexStringOfByteLength, normalizeHex } from '@/utils/hex' import { isUint8, isUint32, isUint64 } from '@/utils/uint' const ARGUMENTS = { ISSUING_UTXOS_COUNT: 'ISSUING_UTXOS_COUNT', REISSUANCE_FLAGS: 'REISSUANCE_FLAGS', - FACTORY_OWNER_PUBKEY: 'FACTORY_OWNER_PUBKEY', } as const const WITNESS = { @@ -23,13 +21,11 @@ const WITNESS = { export interface IssuanceFactoryProgramParams { issuingUtxosCount: number reissuanceFlags: bigint - factoryOwnerPubkey: Uint8Array } export interface IssuanceFactoryWitnessParams { branch: 'IssueAssets' | 'RemoveFactory' outputIndex: number - ownerSignatureHex: string } export function loadIssuanceFactoryProgram( @@ -47,17 +43,10 @@ export function buildIssuanceFactoryArguments( if (!isUint64(params.reissuanceFlags)) { throw new Error('reissuanceFlags must fit into u64') } - if (params.factoryOwnerPubkey.length !== 32) { - throw new Error('factoryOwnerPubkey must be a 32-byte x-only public key') - } return new SimplicityArguments() .addValue(ARGUMENTS.ISSUING_UTXOS_COUNT, SimplicityTypedValue.fromU8(params.issuingUtxosCount)) .addValue(ARGUMENTS.REISSUANCE_FLAGS, SimplicityTypedValue.fromU64(params.reissuanceFlags)) - .addValue( - ARGUMENTS.FACTORY_OWNER_PUBKEY, - SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.factoryOwnerPubkey)), - ) } export function buildIssuanceFactoryWitness( @@ -67,15 +56,9 @@ export function buildIssuanceFactoryWitness( throw new Error('outputIndex must fit into u32') } - const pathType = SimplicityType.fromString('Either<(u32, Signature), (u32, Signature)>') - const ownerSignatureHex = normalizeHex(params.ownerSignatureHex) - if (!isHexStringOfByteLength(ownerSignatureHex, 64)) { - throw new Error('ownerSignatureHex must be a 64-byte Schnorr signature hex string') - } - - const pathParams = `(${params.outputIndex}, 0x${ownerSignatureHex})` + const pathType = SimplicityType.fromString('Either') const pathExpression = - params.branch === 'IssueAssets' ? `Left(${pathParams})` : `Right(${pathParams})` + params.branch === 'IssueAssets' ? `Left(${params.outputIndex})` : `Right(${params.outputIndex})` return new SimplicityWitnessValues().addValue( WITNESS.PATH, diff --git a/web-v2/src/simplicity/lending/program.ts b/web-v2/src/simplicity/lending/program.ts new file mode 100644 index 0000000..a75a272 --- /dev/null +++ b/web-v2/src/simplicity/lending/program.ts @@ -0,0 +1,333 @@ +import { + type Network, + SimplicityArguments, + SimplicityProgram, + SimplicityType, + SimplicityTypedValue, + SimplicityWitnessValues, + type XOnlyPublicKey, +} from 'lwk_web' +import { sources } from 'virtual:simplicity-sources' + +import { type AssetAuthProgramParams, loadAssetAuthProgram } from '@/simplicity/asset-auth/program' +import { + type AssetAuthVaultProgramParams, + buildActiveAssetAuthVaultParams, + buildFinalizedAssetAuthVaultParams, + loadAssetAuthVaultProgram, +} from '@/simplicity/asset-auth-vault/program' +import { bytes32ToHex, hexToBytes } from '@/utils/hex' +import { isUint16, isUint32, isUint64 } from '@/utils/uint' + +const ARGUMENTS = { + COLLATERAL_ASSET_ID: 'COLLATERAL_ASSET_ID', + PRINCIPAL_ASSET_ID: 'PRINCIPAL_ASSET_ID', + BORROWER_NFT_ASSET_ID: 'BORROWER_NFT_ASSET_ID', + LENDER_NFT_ASSET_ID: 'LENDER_NFT_ASSET_ID', + COLLATERAL_AMOUNT: 'COLLATERAL_AMOUNT', + PRINCIPAL_AMOUNT: 'PRINCIPAL_AMOUNT', + PRINCIPAL_INTEREST_RATE: 'PRINCIPAL_INTEREST_RATE', + LOAN_EXPIRATION_TIME: 'LOAN_EXPIRATION_TIME', + LENDER_VAULT_COV_HASH: 'LENDER_VAULT_COV_HASH', + FINALIZED_LENDER_VAULT_COV_HASH: 'FINALIZED_LENDER_VAULT_COV_HASH', + PROTOCOL_FEE_VAULT_COV_HASH: 'PROTOCOL_FEE_VAULT_COV_HASH', + FINALIZED_PROTOCOL_FEE_VAULT_COV_HASH: 'FINALIZED_PROTOCOL_FEE_VAULT_COV_HASH', + PRINCIPAL_OUTPUT_SCRIPT_HASH: 'PRINCIPAL_OUTPUT_SCRIPT_HASH', +} as const + +const WITNESS = { + PATH: 'PATH', +} as const + +export interface OfferParameters { + collateralAmount: bigint + principalAmount: bigint + principalInterestRate: number + loanExpirationTime: number +} + +export interface LendingOfferProgramParams { + collateralAssetId: Uint8Array + principalAssetId: Uint8Array + borrowerNftAssetId: Uint8Array + lenderNftAssetId: Uint8Array + protocolFeeKeeperAssetId: Uint8Array + offerParameters: OfferParameters + lenderVaultCovHash: Uint8Array + finalizedLenderVaultCovHash: Uint8Array + protocolFeeVaultCovHash: Uint8Array + finalizedProtocolFeeVaultCovHash: Uint8Array + principalOutputScriptHash: Uint8Array +} + +export type LendingOfferWitnessParams = + | { branch: 'OfferAcceptance' } + | { branch: 'OfferCancellation' } + | { branch: 'PartialRepayment'; currentDebt: bigint; amountToRepay: bigint } + | { branch: 'FullRepayment'; currentDebt: bigint } + | { branch: 'Liquidation'; currentDebt: bigint } + +export function loadLendingProgram(params: LendingOfferProgramParams): SimplicityProgram { + return SimplicityProgram.load(sources.lending, buildLendingArguments(params)) +} + +export function buildLendingArguments(params: LendingOfferProgramParams): SimplicityArguments { + assertBytes32(params.collateralAssetId, 'collateralAssetId') + assertBytes32(params.principalAssetId, 'principalAssetId') + assertBytes32(params.borrowerNftAssetId, 'borrowerNftAssetId') + assertBytes32(params.lenderNftAssetId, 'lenderNftAssetId') + assertBytes32(params.lenderVaultCovHash, 'lenderVaultCovHash') + assertBytes32(params.finalizedLenderVaultCovHash, 'finalizedLenderVaultCovHash') + assertBytes32(params.protocolFeeVaultCovHash, 'protocolFeeVaultCovHash') + assertBytes32(params.finalizedProtocolFeeVaultCovHash, 'finalizedProtocolFeeVaultCovHash') + assertBytes32(params.principalOutputScriptHash, 'principalOutputScriptHash') + assertOfferParameters(params.offerParameters) + + return new SimplicityArguments() + .addValue( + ARGUMENTS.COLLATERAL_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.collateralAssetId)), + ) + .addValue( + ARGUMENTS.PRINCIPAL_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.principalAssetId)), + ) + .addValue( + ARGUMENTS.BORROWER_NFT_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.borrowerNftAssetId)), + ) + .addValue( + ARGUMENTS.LENDER_NFT_ASSET_ID, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.lenderNftAssetId)), + ) + .addValue( + ARGUMENTS.COLLATERAL_AMOUNT, + SimplicityTypedValue.fromU64(params.offerParameters.collateralAmount), + ) + .addValue( + ARGUMENTS.PRINCIPAL_AMOUNT, + SimplicityTypedValue.fromU64(params.offerParameters.principalAmount), + ) + .addValue( + ARGUMENTS.PRINCIPAL_INTEREST_RATE, + SimplicityTypedValue.fromU64(BigInt(params.offerParameters.principalInterestRate)), + ) + .addValue( + ARGUMENTS.LOAN_EXPIRATION_TIME, + SimplicityTypedValue.fromU32(params.offerParameters.loanExpirationTime), + ) + .addValue( + ARGUMENTS.LENDER_VAULT_COV_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.lenderVaultCovHash)), + ) + .addValue( + ARGUMENTS.FINALIZED_LENDER_VAULT_COV_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.finalizedLenderVaultCovHash)), + ) + .addValue( + ARGUMENTS.PROTOCOL_FEE_VAULT_COV_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.protocolFeeVaultCovHash)), + ) + .addValue( + ARGUMENTS.FINALIZED_PROTOCOL_FEE_VAULT_COV_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.finalizedProtocolFeeVaultCovHash)), + ) + .addValue( + ARGUMENTS.PRINCIPAL_OUTPUT_SCRIPT_HASH, + SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.principalOutputScriptHash)), + ) +} + +export function buildPrincipalOutputAssetAuthParams( + params: Pick, +): AssetAuthProgramParams { + return { + assetId: params.borrowerNftAssetId, + assetAmount: getTotalAmountToRepay(params.offerParameters), + withAssetBurn: false, + } +} + +export function buildFinalizedLenderVaultParams( + params: Pick< + LendingOfferProgramParams, + 'principalAssetId' | 'lenderNftAssetId' | 'borrowerNftAssetId' + >, +): AssetAuthVaultProgramParams { + return buildFinalizedAssetAuthVaultParams({ + vaultAssetId: params.principalAssetId, + keeperAuthAssetId: params.lenderNftAssetId, + keeperAuthAssetAmount: 1n, + withKeeperAssetBurn: true, + supplierAuthAssetId: params.borrowerNftAssetId, + withSupplierAssetBurn: true, + }) +} + +export function buildFinalizedProtocolFeeVaultParams( + params: Pick< + LendingOfferProgramParams, + 'principalAssetId' | 'protocolFeeKeeperAssetId' | 'borrowerNftAssetId' + >, +): AssetAuthVaultProgramParams { + return buildFinalizedAssetAuthVaultParams({ + vaultAssetId: params.principalAssetId, + keeperAuthAssetId: params.protocolFeeKeeperAssetId, + keeperAuthAssetAmount: 1n, + withKeeperAssetBurn: false, + supplierAuthAssetId: params.borrowerNftAssetId, + withSupplierAssetBurn: true, + }) +} + +export function buildLendingWitness(params: LendingOfferWitnessParams): SimplicityWitnessValues { + const pathType = SimplicityType.fromString( + 'Either, Either, u64>>', + ) + + return new SimplicityWitnessValues().addValue( + WITNESS.PATH, + SimplicityTypedValue.parse(buildLendingPathExpression(params), pathType), + ) +} + +export function buildDerivedLendingOfferProgramParams( + params: Omit< + LendingOfferProgramParams, + | 'lenderVaultCovHash' + | 'finalizedLenderVaultCovHash' + | 'protocolFeeVaultCovHash' + | 'finalizedProtocolFeeVaultCovHash' + | 'principalOutputScriptHash' + >, + internalKey: XOnlyPublicKey, + network: Network, +): LendingOfferProgramParams { + const principalOutputAssetAuth = loadAssetAuthProgram(buildPrincipalOutputAssetAuthParams(params)) + const finalizedLenderVault = loadAssetAuthVaultProgram(buildFinalizedLenderVaultParams(params)) + const finalizedProtocolFeeVault = loadAssetAuthVaultProgram( + buildFinalizedProtocolFeeVaultParams(params), + ) + const finalizedLenderVaultCovHash = getProgramScriptHash( + finalizedLenderVault, + internalKey, + network, + ) + const finalizedProtocolFeeVaultCovHash = getProgramScriptHash( + finalizedProtocolFeeVault, + internalKey, + network, + ) + const activeLenderVault = loadAssetAuthVaultProgram( + buildActiveAssetAuthVaultParams( + buildFinalizedLenderVaultParams(params), + finalizedLenderVaultCovHash, + ), + ) + const activeProtocolFeeVault = loadAssetAuthVaultProgram( + buildActiveAssetAuthVaultParams( + buildFinalizedProtocolFeeVaultParams(params), + finalizedProtocolFeeVaultCovHash, + ), + ) + + return { + ...params, + lenderVaultCovHash: getProgramScriptHash(activeLenderVault, internalKey, network), + finalizedLenderVaultCovHash, + protocolFeeVaultCovHash: getProgramScriptHash(activeProtocolFeeVault, internalKey, network), + finalizedProtocolFeeVaultCovHash, + principalOutputScriptHash: getProgramScriptHash(principalOutputAssetAuth, internalKey, network), + } +} + +export function getProgramScriptHash( + program: SimplicityProgram, + internalKey: XOnlyPublicKey, + network: Network, +): Uint8Array { + return hexToBytes(program.createP2trAddress(internalKey, network).scriptPubkey().jet_sha256_hex()) +} + +export function getTotalAmountToRepay(params: OfferParameters): bigint { + assertOfferParameters(params) + + return ( + params.principalAmount + + (params.principalAmount * BigInt(params.principalInterestRate)) / 10_000n + ) +} + +export async function buildPendingOfferMetadata(params: { + principalAssetId: Uint8Array + offerParameters: Pick< + OfferParameters, + 'principalAmount' | 'loanExpirationTime' | 'principalInterestRate' + > +}): Promise { + assertBytes32(params.principalAssetId, 'principalAssetId') + assertUint64(params.offerParameters.principalAmount, 'principalAmount') + if (!isUint32(params.offerParameters.loanExpirationTime)) { + throw new Error('loanExpirationTime must fit into u32') + } + if (!isUint16(params.offerParameters.principalInterestRate)) { + throw new Error('principalInterestRate must fit into u16') + } + + const programId = await getLendingProgramId() + const data = new Uint8Array(50) + const view = new DataView(data.buffer) + data.set(programId, 0) + data.set(params.principalAssetId, 4) + view.setBigUint64(36, params.offerParameters.principalAmount, true) + view.setUint32(44, params.offerParameters.loanExpirationTime, true) + view.setUint16(48, params.offerParameters.principalInterestRate, true) + return data +} + +async function getLendingProgramId(): Promise { + const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(sources.lending)) + return new Uint8Array(hash).slice(0, 4) +} + +function buildLendingPathExpression(params: LendingOfferWitnessParams): string { + switch (params.branch) { + case 'OfferAcceptance': + return 'Left(Left(()))' + case 'OfferCancellation': + return 'Left(Right(()))' + case 'PartialRepayment': + assertUint64(params.currentDebt, 'currentDebt') + assertUint64(params.amountToRepay, 'amountToRepay') + return `Right(Left(Left((${params.currentDebt}, ${params.amountToRepay}))))` + case 'FullRepayment': + assertUint64(params.currentDebt, 'currentDebt') + return `Right(Left(Right(${params.currentDebt})))` + case 'Liquidation': + assertUint64(params.currentDebt, 'currentDebt') + return `Right(Right(${params.currentDebt}))` + } +} + +function assertOfferParameters(params: OfferParameters): void { + assertUint64(params.collateralAmount, 'collateralAmount') + assertUint64(params.principalAmount, 'principalAmount') + if (!isUint16(params.principalInterestRate)) { + throw new Error('principalInterestRate must fit into u16') + } + if (!isUint32(params.loanExpirationTime)) { + throw new Error('loanExpirationTime must fit into u32') + } +} + +function assertBytes32(value: Uint8Array, label: string): void { + if (value.length !== 32) { + throw new Error(`${label} must be 32 bytes`) + } +} + +function assertUint64(value: bigint, label: string): void { + if (!isUint64(value)) { + throw new Error(`${label} must fit into u64`) + } +} diff --git a/web-v2/src/utils/uint.ts b/web-v2/src/utils/uint.ts index 9966ce6..089d4e8 100644 --- a/web-v2/src/utils/uint.ts +++ b/web-v2/src/utils/uint.ts @@ -1,4 +1,5 @@ const MAX_U8 = 0xff +const MAX_U16 = 0xffff const MAX_U32 = 0xffff_ffff const MAX_U64 = (1n << 64n) - 1n @@ -6,6 +7,10 @@ export function isUint8(value: number): boolean { return Number.isInteger(value) && value >= 0 && value <= MAX_U8 } +export function isUint16(value: number): boolean { + return Number.isInteger(value) && value >= 0 && value <= MAX_U16 +} + export function isUint32(value: number): boolean { return Number.isInteger(value) && value >= 0 && value <= MAX_U32 } @@ -13,3 +18,21 @@ export function isUint32(value: number): boolean { export function isUint64(value: bigint): boolean { return value >= 0n && value <= MAX_U64 } + +export function assertBytes32(value: Uint8Array, label: string): void { + if (value.length !== 32) { + throw new Error(`${label} must be 32 bytes`) + } +} + +export function assertUint32(value: number, label: string): void { + if (!isUint32(value)) { + throw new Error(`${label} must fit into u32`) + } +} + +export function assertUint64(value: bigint, label: string): void { + if (!isUint64(value)) { + throw new Error(`${label} must fit into u64`) + } +} diff --git a/web-v2/src/vite-env.d.ts b/web-v2/src/vite-env.d.ts index 89b79b7..a435427 100644 --- a/web-v2/src/vite-env.d.ts +++ b/web-v2/src/vite-env.d.ts @@ -13,6 +13,7 @@ declare module 'virtual:simplicity-sources' { export interface SimplicitySources { lending: string asset_auth: string + asset_auth_vault: string script_auth: string issuance_factory: string } From 95ddeaad2e7b0ba9dfd8a49f7be718a9e03a6a1d Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 5 Jun 2026 15:04:02 +0300 Subject: [PATCH 09/16] feat(asset-auth): implement asset authorization program feat(asset-auth-vault): add asset authorization vault program feat(lending): create lending program with offer management --- web-v2/Dockerfile | 9 +- web-v2/docs/HOW_TO_BUILD_LWK_WASM.md | 12 +- web-v2/package.json | 1 + web-v2/pnpm-lock.yaml | 3628 +++++------------ .../src/pages/Dashboard/CreateOfferDemo.tsx | 570 +-- 5 files changed, 1221 insertions(+), 2999 deletions(-) diff --git a/web-v2/Dockerfile b/web-v2/Dockerfile index fad13d9..ee39ad2 100644 --- a/web-v2/Dockerfile +++ b/web-v2/Dockerfile @@ -4,16 +4,17 @@ FROM rust:1.91-bookworm AS lwk-builder ARG LWK_REF=wasm-0.16-expose-external-utxos # ARG LWK_COMMIT=4786e456db6efc05413721f6489899a6426eaa47 -ARG LWK_COMMIT=c5d422821cf19aa0bf1a11f669a4214b04bde3eb +ARG LWK_COMMIT=1ca9d0414baa599925b8b1d412159131dd76d87d RUN apt-get update && apt-get install -y --no-install-recommends \ git pkg-config libssl-dev ca-certificates clang \ && rm -rf /var/lib/apt/lists/* RUN rustup target add wasm32-unknown-unknown RUN cargo install wasm-pack --locked -# RUN git clone --depth 1 --branch "${LWK_REF}" https://github.com/Blockstream/lwk.git /tmp/lwk -RUN git clone --depth 1 --branch "${LWK_REF}" https://github.com/lilbonekit/lwk.git /tmp/lwk -RUN test "$(git -C /tmp/lwk rev-parse HEAD)" = "${LWK_COMMIT}" +# RUN git clone --filter=blob:none --branch "${LWK_REF}" https://github.com/Blockstream/lwk.git /tmp/lwk +RUN git clone --filter=blob:none --branch "${LWK_REF}" https://github.com/lilbonekit/lwk.git /tmp/lwk \ + && git -C /tmp/lwk checkout --detach "${LWK_COMMIT}" \ + && test "$(git -C /tmp/lwk rev-parse HEAD)" = "${LWK_COMMIT}" WORKDIR /tmp/lwk/lwk_wasm RUN RUSTFLAGS='--cfg web_sys_unstable_apis' wasm-pack build --target web --out-dir pkg_web --features simplicity,serial diff --git a/web-v2/docs/HOW_TO_BUILD_LWK_WASM.md b/web-v2/docs/HOW_TO_BUILD_LWK_WASM.md index d2399f7..ea070ec 100644 --- a/web-v2/docs/HOW_TO_BUILD_LWK_WASM.md +++ b/web-v2/docs/HOW_TO_BUILD_LWK_WASM.md @@ -15,6 +15,7 @@ docker build -f web-v2/Dockerfile --target lwk-builder -t lwk-builder . docker create --name tmp lwk-builder docker cp tmp:/tmp/lwk/lwk_wasm/pkg_web ./pkg_web_from_docker docker rm tmp +rm -rf ./lwk_wasm/pkg_web mkdir -p ./lwk_wasm mv ./pkg_web_from_docker ./lwk_wasm/pkg_web ``` @@ -23,6 +24,15 @@ mv ./pkg_web_from_docker ./lwk_wasm/pkg_web ```bash cd web-v2 -pnpm install +pnpm install --force +rm -rf node_modules/.vite pnpm dev ``` + +If `node_modules` already existed, `pnpm install --force` is important because `lwk_web` is a local `file:` dependency. Clearing only `node_modules/.vite` refreshes Vite's prebundle cache, but it does not guarantee that the installed `lwk_web` package was refreshed. + +## **4. Optional check after install**: + +```bash +shasum -a 256 ../lwk_wasm/pkg_web/lwk_wasm_bg.wasm node_modules/lwk_web/lwk_wasm_bg.wasm +``` diff --git a/web-v2/package.json b/web-v2/package.json index 38f959a..a725a63 100644 --- a/web-v2/package.json +++ b/web-v2/package.json @@ -24,6 +24,7 @@ "lwk_web": "file:../lwk_wasm/pkg_web", "react": "^19.2.6", "react-dom": "^19.2.6", + "react-hook-form": "^7.77.0", "react-router-dom": "^7.15.0", "zod": "^3.25.76" }, diff --git a/web-v2/pnpm-lock.yaml b/web-v2/pnpm-lock.yaml index b01e6c3..27f0dda 100644 --- a/web-v2/pnpm-lock.yaml +++ b/web-v2/pnpm-lock.yaml @@ -5,6 +5,7 @@ settings: excludeLinksFromLockfile: false importers: + .: dependencies: '@fontsource-variable/inter': @@ -28,6 +29,9 @@ importers: react-dom: specifier: ^19.2.6 version: 19.2.6(react@19.2.6) + react-hook-form: + specifier: ^7.77.0 + version: 7.77.0(react@19.2.6) react-router-dom: specifier: ^7.15.0 version: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -127,666 +131,406 @@ importers: version: 0.13.0(eslint@9.39.4(jiti@2.7.0))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.3(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.21.0)) packages: + '@adobe/react-spectrum-ui@1.2.1': - resolution: - { - integrity: sha512-wcrbEE2O/9WnEn6avBnaVRRx88S5PLFsPLr4wffzlbMfXeQsy+RMQwaJd3cbzrn18/j04Isit7f7Emfn0dhrJA==, - } + resolution: {integrity: sha512-wcrbEE2O/9WnEn6avBnaVRRx88S5PLFsPLr4wffzlbMfXeQsy+RMQwaJd3cbzrn18/j04Isit7f7Emfn0dhrJA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@adobe/react-spectrum-workflow@2.3.5': - resolution: - { - integrity: sha512-b53VIPwPWKb/T5gzE3qs+QlGP5gVrw/LnWV3xMksDU+CRl3rzOKUwxIGiZO8ICyYh1WiyqY4myGlPU/nAynBUg==, - } + resolution: {integrity: sha512-b53VIPwPWKb/T5gzE3qs+QlGP5gVrw/LnWV3xMksDU+CRl3rzOKUwxIGiZO8ICyYh1WiyqY4myGlPU/nAynBUg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 '@adobe/react-spectrum@3.47.0': - resolution: - { - integrity: sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==, - } + resolution: {integrity: sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@alloc/quick-lru@5.2.0': - resolution: - { - integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} '@babel/code-frame@7.29.0': - resolution: - { - integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} '@babel/compat-data@7.29.3': - resolution: - { - integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + engines: {node: '>=6.9.0'} '@babel/core@7.29.0': - resolution: - { - integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} '@babel/generator@7.29.1': - resolution: - { - integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.28.6': - resolution: - { - integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} '@babel/helper-globals@7.28.0': - resolution: - { - integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} '@babel/helper-module-imports@7.28.6': - resolution: - { - integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} '@babel/helper-module-transforms@7.28.6': - resolution: - { - integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 '@babel/helper-plugin-utils@7.28.6': - resolution: - { - integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.27.1': - resolution: - { - integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} '@babel/helper-validator-identifier@7.28.5': - resolution: - { - integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': - resolution: - { - integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} '@babel/helpers@7.29.2': - resolution: - { - integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} '@babel/parser@7.29.3': - resolution: - { - integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==, - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} hasBin: true '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: - { - integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: - { - integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 '@babel/runtime@7.29.2': - resolution: - { - integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} '@babel/template@7.28.6': - resolution: - { - integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} '@babel/traverse@7.29.0': - resolution: - { - integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} '@babel/types@7.29.0': - resolution: - { - integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} '@emnapi/core@1.10.0': - resolution: - { - integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==, - } + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} '@emnapi/runtime@1.10.0': - resolution: - { - integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==, - } + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/wasi-threads@1.2.1': - resolution: - { - integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==, - } + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@esbuild/aix-ppc64@0.27.7': - resolution: - { - integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] '@esbuild/android-arm64@0.27.7': - resolution: - { - integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} cpu: [arm64] os: [android] '@esbuild/android-arm@0.27.7': - resolution: - { - integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} cpu: [arm] os: [android] '@esbuild/android-x64@0.27.7': - resolution: - { - integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} cpu: [x64] os: [android] '@esbuild/darwin-arm64@0.27.7': - resolution: - { - integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.27.7': - resolution: - { - integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] '@esbuild/freebsd-arm64@0.27.7': - resolution: - { - integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.27.7': - resolution: - { - integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] '@esbuild/linux-arm64@0.27.7': - resolution: - { - integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.27.7': - resolution: - { - integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.27.7': - resolution: - { - integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.27.7': - resolution: - { - integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.27.7': - resolution: - { - integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.27.7': - resolution: - { - integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.27.7': - resolution: - { - integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.27.7': - resolution: - { - integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.27.7': - resolution: - { - integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} cpu: [x64] os: [linux] '@esbuild/netbsd-arm64@0.27.7': - resolution: - { - integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.27.7': - resolution: - { - integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] '@esbuild/openbsd-arm64@0.27.7': - resolution: - { - integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.27.7': - resolution: - { - integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] '@esbuild/openharmony-arm64@0.27.7': - resolution: - { - integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} cpu: [arm64] os: [openharmony] '@esbuild/sunos-x64@0.27.7': - resolution: - { - integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] '@esbuild/win32-arm64@0.27.7': - resolution: - { - integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.27.7': - resolution: - { - integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.27.7': - resolution: - { - integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} cpu: [x64] os: [win32] '@eslint-community/eslint-utils@4.9.1': - resolution: - { - integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 '@eslint-community/regexpp@4.12.2': - resolution: - { - integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.21.2': - resolution: - { - integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/config-helpers@0.4.2': - resolution: - { - integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/core@0.17.0': - resolution: - { - integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.5': - resolution: - { - integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/js@9.39.4': - resolution: - { - integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.7': - resolution: - { - integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/plugin-kit@0.4.1': - resolution: - { - integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fontsource-variable/inter@5.2.8': - resolution: - { - integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==, - } + resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} '@formatjs/ecma402-abstract@2.3.6': - resolution: - { - integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==, - } + resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==} '@formatjs/fast-memoize@2.2.7': - resolution: - { - integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==, - } + resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==} '@formatjs/icu-messageformat-parser@2.11.4': - resolution: - { - integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==, - } + resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==} '@formatjs/icu-skeleton-parser@1.8.16': - resolution: - { - integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==, - } + resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==} '@formatjs/intl-localematcher@0.6.2': - resolution: - { - integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==, - } + resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} '@heroui/react@3.0.4': - resolution: - { - integrity: sha512-6y6aXZ9v8W/dNDKA3yP0+VnY3OQUeg8164YnOXVaFd1U8+WbXka1hwPgi3PK/qmMv8W81SdiVnmEZUXjHbXpfw==, - } + resolution: {integrity: sha512-6y6aXZ9v8W/dNDKA3yP0+VnY3OQUeg8164YnOXVaFd1U8+WbXka1hwPgi3PK/qmMv8W81SdiVnmEZUXjHbXpfw==} peerDependencies: react: '>=19.0.0' react-dom: '>=19.0.0' tailwindcss: '>=4.0.0' '@heroui/styles@3.0.4': - resolution: - { - integrity: sha512-3zi0cu8PPmMbUdRg7AO486w9uA7ZHuV6m8ab23RQHChJ97q3s/tM13r74l7A963Dif+0ZWpQRiicsUvWuaAcDg==, - } + resolution: {integrity: sha512-3zi0cu8PPmMbUdRg7AO486w9uA7ZHuV6m8ab23RQHChJ97q3s/tM13r74l7A963Dif+0ZWpQRiicsUvWuaAcDg==} peerDependencies: tailwindcss: '>=4.0.0' '@humanfs/core@0.19.2': - resolution: - { - integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==, - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} '@humanfs/node@0.16.8': - resolution: - { - integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==, - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} '@humanfs/types@0.15.0': - resolution: - { - integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==, - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: '>=12.22' } + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} '@humanwhocodes/retry@0.4.3': - resolution: - { - integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, - } - engines: { node: '>=18.18' } + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} '@internationalized/date@3.12.1': - resolution: - { - integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==, - } + resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==} '@internationalized/message@3.1.9': - resolution: - { - integrity: sha512-x03MSVTaB/4JHtW1VAYaY/2cCuBrHbWM6ZvlgpKdnSdW28tZbqpR673RJrVJyXWRw1bpgYN89Tz7ohX5tgNgPA==, - } + resolution: {integrity: sha512-x03MSVTaB/4JHtW1VAYaY/2cCuBrHbWM6ZvlgpKdnSdW28tZbqpR673RJrVJyXWRw1bpgYN89Tz7ohX5tgNgPA==} '@internationalized/number@3.6.6': - resolution: - { - integrity: sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==, - } + resolution: {integrity: sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==} '@internationalized/string@3.2.8': - resolution: - { - integrity: sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==, - } + resolution: {integrity: sha512-NdbMQUSfXLYIQol5VyMtinm9pZDciiMfN7RtmSuSB78io1hqwJ0naYfxyW6vgxWBkzWymQa/3uLDlbfmshtCaA==} '@jridgewell/gen-mapping@0.3.13': - resolution: - { - integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, - } + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/remapping@2.3.5': - resolution: - { - integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, - } + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} '@jridgewell/resolve-uri@3.1.2': - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} '@jridgewell/sourcemap-codec@1.5.5': - resolution: - { - integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, - } + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} '@jridgewell/trace-mapping@0.3.31': - resolution: - { - integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, - } + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@napi-rs/wasm-runtime@0.2.12': - resolution: - { - integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, - } + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} '@pkgr/core@0.2.9': - resolution: - { - integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} '@radix-ui/react-avatar@1.1.11': - resolution: - { - integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==, - } + resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -799,10 +543,7 @@ packages: optional: true '@radix-ui/react-compose-refs@1.1.2': - resolution: - { - integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==, - } + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -811,10 +552,7 @@ packages: optional: true '@radix-ui/react-context@1.1.3': - resolution: - { - integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==, - } + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -823,10 +561,7 @@ packages: optional: true '@radix-ui/react-primitive@2.1.4': - resolution: - { - integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==, - } + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -839,10 +574,7 @@ packages: optional: true '@radix-ui/react-slot@1.2.4': - resolution: - { - integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==, - } + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -851,10 +583,7 @@ packages: optional: true '@radix-ui/react-use-callback-ref@1.1.1': - resolution: - { - integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==, - } + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -863,10 +592,7 @@ packages: optional: true '@radix-ui/react-use-is-hydrated@0.1.0': - resolution: - { - integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==, - } + resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -875,10 +601,7 @@ packages: optional: true '@radix-ui/react-use-layout-effect@1.1.1': - resolution: - { - integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==, - } + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -887,440 +610,298 @@ packages: optional: true '@react-aria/color@3.2.0': - resolution: - { - integrity: sha512-Qw1TySxXnGlE4L7kzsi8v86U1yFs9FtonqsbySFzLPzsMV1Oar+rtkYHI5vwNSyNNF6TBJJikJNocS9Fi8xXwA==, - } + resolution: {integrity: sha512-Qw1TySxXnGlE4L7kzsi8v86U1yFs9FtonqsbySFzLPzsMV1Oar+rtkYHI5vwNSyNNF6TBJJikJNocS9Fi8xXwA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-aria/i18n@3.13.0': - resolution: - { - integrity: sha512-APjw4EwmvlnIyDxixSWfjHvOFFkW2rVTyKZ4l9FV0v7hOerh+FWLE6mF1XnnX3pgz3yARkKWwhSR9xYcRK6tpg==, - } + resolution: {integrity: sha512-APjw4EwmvlnIyDxixSWfjHvOFFkW2rVTyKZ4l9FV0v7hOerh+FWLE6mF1XnnX3pgz3yARkKWwhSR9xYcRK6tpg==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-aria/ssr@3.10.0': - resolution: - { - integrity: sha512-mnelvACtfNWWKFCT1YHebxJRmfBmmANGwHQhCFPByMVTx1L8RumcaLxChYkE87g2KPuP5xX2il/oRn1DytW+qQ==, - } - engines: { node: '>= 12' } + resolution: {integrity: sha512-mnelvACtfNWWKFCT1YHebxJRmfBmmANGwHQhCFPByMVTx1L8RumcaLxChYkE87g2KPuP5xX2il/oRn1DytW+qQ==} + engines: {node: '>= 12'} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-aria/utils@3.34.0': - resolution: - { - integrity: sha512-ZM1ZXIqpwGTJjjL6o3JhlZkEaBpQdxuOCqLEvwEwooaj5GsYI3E9UfOl5vy3UW6bYiEEWl9pNBntrb9CR9kItQ==, - } + resolution: {integrity: sha512-ZM1ZXIqpwGTJjjL6o3JhlZkEaBpQdxuOCqLEvwEwooaj5GsYI3E9UfOl5vy3UW6bYiEEWl9pNBntrb9CR9kItQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-spectrum/color@3.2.0': - resolution: - { - integrity: sha512-Xg/U8+l1CQdvPRF4Zrv7AvtqsjuYUNkMxJMG0cIug9RKtIfEoyh7VR4Xg3FNd4Y/AwKXNJZZN4l94qz4WlK23Q==, - } + resolution: {integrity: sha512-Xg/U8+l1CQdvPRF4Zrv7AvtqsjuYUNkMxJMG0cIug9RKtIfEoyh7VR4Xg3FNd4Y/AwKXNJZZN4l94qz4WlK23Q==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-spectrum/provider@3.11.0': - resolution: - { - integrity: sha512-W2Gxbj8AcG5OR2K5Ua3K8qQqxdsiytEiz+2rhr6oQyBM8VafEgDcNPYSOTtfjrQM3snl2Uhp8LzwN0jwQe/6nQ==, - } + resolution: {integrity: sha512-W2Gxbj8AcG5OR2K5Ua3K8qQqxdsiytEiz+2rhr6oQyBM8VafEgDcNPYSOTtfjrQM3snl2Uhp8LzwN0jwQe/6nQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-stately/color@3.10.0': - resolution: - { - integrity: sha512-P4tlvOYFA8hl/NXiMyPxfM+7rXV01hnwlvGCwbZqUK1aRv0Ry0yGCj2AbSzhYHx7i4J4+CVUJUYozNLzhm+6Sw==, - } + resolution: {integrity: sha512-P4tlvOYFA8hl/NXiMyPxfM+7rXV01hnwlvGCwbZqUK1aRv0Ry0yGCj2AbSzhYHx7i4J4+CVUJUYozNLzhm+6Sw==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-stately/utils@3.12.0': - resolution: - { - integrity: sha512-7q+iHz9cENvro1dVKgdTxNh1i1mtWuLUI6UHp10TAgpxM9DyRDvmuN35zLXYCmMDgx3WLY2xkwqoez8xd+CdxQ==, - } + resolution: {integrity: sha512-7q+iHz9cENvro1dVKgdTxNh1i1mtWuLUI6UHp10TAgpxM9DyRDvmuN35zLXYCmMDgx3WLY2xkwqoez8xd+CdxQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-types/color@3.2.0': - resolution: - { - integrity: sha512-beV3vz80nzZ1EuYUM7296Kyi3AHcMrbQw0qub/9yzHWVTKKc5sy/e4dCMKcWL/ArkeAyc7jDOiui190RQ4l0Fw==, - } + resolution: {integrity: sha512-beV3vz80nzZ1EuYUM7296Kyi3AHcMrbQw0qub/9yzHWVTKKc5sy/e4dCMKcWL/ArkeAyc7jDOiui190RQ4l0Fw==} peerDependencies: '@react-spectrum/provider': ^3.0.0 react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@react-types/shared@3.34.0': - resolution: - { - integrity: sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==, - } + resolution: {integrity: sha512-gp6xo/s2lX54AlTjOiqwDnxA7UW79BNvI9dB9pr3LZTzRKCd1ZA+ZbgKw/ReIiWuvvVw/8QFJpnqeeFyLocMcQ==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@rolldown/pluginutils@1.0.0-rc.3': - resolution: - { - integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==, - } + resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} '@rollup/rollup-android-arm-eabi@4.60.3': - resolution: - { - integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==, - } + resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} cpu: [arm] os: [android] '@rollup/rollup-android-arm64@4.60.3': - resolution: - { - integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==, - } + resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} cpu: [arm64] os: [android] '@rollup/rollup-darwin-arm64@4.60.3': - resolution: - { - integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==, - } + resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} cpu: [arm64] os: [darwin] '@rollup/rollup-darwin-x64@4.60.3': - resolution: - { - integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==, - } + resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} cpu: [x64] os: [darwin] '@rollup/rollup-freebsd-arm64@4.60.3': - resolution: - { - integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==, - } + resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} cpu: [arm64] os: [freebsd] '@rollup/rollup-freebsd-x64@4.60.3': - resolution: - { - integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==, - } + resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} cpu: [x64] os: [freebsd] '@rollup/rollup-linux-arm-gnueabihf@4.60.3': - resolution: - { - integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==, - } + resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.3': - resolution: - { - integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==, - } + resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.3': - resolution: - { - integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==, - } + resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.3': - resolution: - { - integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==, - } + resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.3': - resolution: - { - integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==, - } + resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.3': - resolution: - { - integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==, - } + resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.3': - resolution: - { - integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==, - } + resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.3': - resolution: - { - integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==, - } + resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.3': - resolution: - { - integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==, - } + resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.3': - resolution: - { - integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==, - } + resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.3': - resolution: - { - integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==, - } + resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.3': - resolution: - { - integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==, - } + resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.3': - resolution: - { - integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==, - } + resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.3': - resolution: - { - integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==, - } + resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} cpu: [x64] os: [openbsd] '@rollup/rollup-openharmony-arm64@4.60.3': - resolution: - { - integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==, - } + resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} cpu: [arm64] os: [openharmony] '@rollup/rollup-win32-arm64-msvc@4.60.3': - resolution: - { - integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==, - } + resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} cpu: [arm64] os: [win32] '@rollup/rollup-win32-ia32-msvc@4.60.3': - resolution: - { - integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==, - } + resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} cpu: [ia32] os: [win32] '@rollup/rollup-win32-x64-gnu@4.60.3': - resolution: - { - integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==, - } + resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} cpu: [x64] os: [win32] '@rollup/rollup-win32-x64-msvc@4.60.3': - resolution: - { - integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==, - } + resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} cpu: [x64] os: [win32] '@rtsao/scc@1.1.0': - resolution: - { - integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==, - } + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} '@sec-ant/readable-stream@0.4.1': - resolution: - { - integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==, - } + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} '@sindresorhus/merge-streams@4.0.0': - resolution: - { - integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} '@spectrum-icons/ui@3.7.0': - resolution: - { - integrity: sha512-86iQSDfJb3Ama1WSJ/mEiFy4DJT7e/v4pSmEuX4aKKMzbNYft+O40N18S2POUnmblrb7MQneLC/pgIp1SDBwEQ==, - } + resolution: {integrity: sha512-86iQSDfJb3Ama1WSJ/mEiFy4DJT7e/v4pSmEuX4aKKMzbNYft+O40N18S2POUnmblrb7MQneLC/pgIp1SDBwEQ==} peerDependencies: '@adobe/react-spectrum': ^3.47.0 react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@spectrum-icons/workflow@4.3.0': - resolution: - { - integrity: sha512-ILuhgWh9jMXaEVMRuOYgTAjMc22cKyvCtUDyZmc8OEMfOYuejj+Gcp5t6DhaCfE0M9rORtVxCrRgsO2WyEgfUw==, - } + resolution: {integrity: sha512-ILuhgWh9jMXaEVMRuOYgTAjMc22cKyvCtUDyZmc8OEMfOYuejj+Gcp5t6DhaCfE0M9rORtVxCrRgsO2WyEgfUw==} peerDependencies: '@adobe/react-spectrum': ^3.47.0 react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 '@swc/helpers@0.5.21': - resolution: - { - integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==, - } + resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} '@tailwindcss/node@4.3.0': - resolution: - { - integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==, - } + resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} '@tailwindcss/oxide-android-arm64@4.3.0': - resolution: - { - integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] '@tailwindcss/oxide-darwin-arm64@4.3.0': - resolution: - { - integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] '@tailwindcss/oxide-darwin-x64@4.3.0': - resolution: - { - integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] '@tailwindcss/oxide-freebsd-x64@4.3.0': - resolution: - { - integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.0': - resolution: - { - integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] '@tailwindcss/oxide-linux-arm64-gnu@4.3.0': - resolution: - { - integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.3.0': - resolution: - { - integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.0': - resolution: - { - integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.0': - resolution: - { - integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.0': - resolution: - { - integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==, - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} + engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: - '@napi-rs/wasm-runtime' @@ -1331,773 +912,463 @@ packages: - tslib '@tailwindcss/oxide-win32-arm64-msvc@4.3.0': - resolution: - { - integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] '@tailwindcss/oxide-win32-x64-msvc@4.3.0': - resolution: - { - integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] '@tailwindcss/oxide@4.3.0': - resolution: - { - integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==, - } - engines: { node: '>= 20' } + resolution: {integrity: sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==} + engines: {node: '>= 20'} '@tailwindcss/postcss@4.3.0': - resolution: - { - integrity: sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==, - } + resolution: {integrity: sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==} '@tanstack/query-core@5.100.10': - resolution: - { - integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==, - } + resolution: {integrity: sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==} '@tanstack/query-devtools@5.100.10': - resolution: - { - integrity: sha512-3DmJf25hDPus5IpVvp6ujXv6bKV2zPzI9vpbAmpJigsL/H6DPvPjmf7/Q9yVKEke//8fgeQ45abjgnLuyYxAiw==, - } + resolution: {integrity: sha512-3DmJf25hDPus5IpVvp6ujXv6bKV2zPzI9vpbAmpJigsL/H6DPvPjmf7/Q9yVKEke//8fgeQ45abjgnLuyYxAiw==} '@tanstack/react-query-devtools@5.100.10': - resolution: - { - integrity: sha512-zes0+o9ef5rAZXJ9f/SeaLs2nufJaeVkZkl/Or9NGrWVF41kL9Od9ED9nCwtQlgiF2VGtrzhEw5AU/igAO+aAg==, - } + resolution: {integrity: sha512-zes0+o9ef5rAZXJ9f/SeaLs2nufJaeVkZkl/Or9NGrWVF41kL9Od9ED9nCwtQlgiF2VGtrzhEw5AU/igAO+aAg==} peerDependencies: '@tanstack/react-query': ^5.100.10 react: ^18 || ^19 '@tanstack/react-query@5.100.10': - resolution: - { - integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==, - } + resolution: {integrity: sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==} peerDependencies: react: ^18 || ^19 '@tybys/wasm-util@0.10.2': - resolution: - { - integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==, - } + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} '@types/babel__core@7.20.5': - resolution: - { - integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, - } + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} '@types/babel__generator@7.27.0': - resolution: - { - integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, - } + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.4': - resolution: - { - integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, - } + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} '@types/babel__traverse@7.28.0': - resolution: - { - integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==, - } + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} '@types/estree@1.0.8': - resolution: - { - integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, - } + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} '@types/estree@1.0.9': - resolution: - { - integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==, - } + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/json-schema@7.0.15': - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, - } + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/json5@0.0.29': - resolution: - { - integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==, - } + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} '@types/node@24.12.4': - resolution: - { - integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==, - } + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} '@types/react-dom@19.2.3': - resolution: - { - integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==, - } + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 '@types/react@19.2.14': - resolution: - { - integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==, - } + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@typescript-eslint/eslint-plugin@8.59.3': - resolution: - { - integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.59.3 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/parser@8.59.3': - resolution: - { - integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/project-service@8.59.3': - resolution: - { - integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/scope-manager@8.59.3': - resolution: - { - integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/tsconfig-utils@8.59.3': - resolution: - { - integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/type-utils@8.59.3': - resolution: - { - integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/types@8.59.3': - resolution: - { - integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@typescript-eslint/typescript-estree@8.59.3': - resolution: - { - integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/utils@8.59.3': - resolution: - { - integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' '@typescript-eslint/visitor-keys@8.59.3': - resolution: - { - integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': - resolution: - { - integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==, - } + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] os: [android] '@unrs/resolver-binding-android-arm64@1.11.1': - resolution: - { - integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==, - } + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} cpu: [arm64] os: [android] '@unrs/resolver-binding-darwin-arm64@1.11.1': - resolution: - { - integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==, - } + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} cpu: [arm64] os: [darwin] '@unrs/resolver-binding-darwin-x64@1.11.1': - resolution: - { - integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==, - } + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} cpu: [x64] os: [darwin] '@unrs/resolver-binding-freebsd-x64@1.11.1': - resolution: - { - integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==, - } + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} cpu: [x64] os: [freebsd] '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': - resolution: - { - integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==, - } + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} cpu: [arm] os: [linux] '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': - resolution: - { - integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==, - } + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} cpu: [arm] os: [linux] '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': - resolution: - { - integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==, - } + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': - resolution: - { - integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==, - } + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': - resolution: - { - integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==, - } + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': - resolution: - { - integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==, - } + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': - resolution: - { - integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==, - } + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': - resolution: - { - integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==, - } + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': - resolution: - { - integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==, - } + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': - resolution: - { - integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==, - } + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': - resolution: - { - integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==, - } - engines: { node: '>=14.0.0' } + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} cpu: [wasm32] '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': - resolution: - { - integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==, - } + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} cpu: [arm64] os: [win32] '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': - resolution: - { - integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==, - } + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} cpu: [ia32] os: [win32] '@unrs/resolver-binding-win32-x64-msvc@1.11.1': - resolution: - { - integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==, - } + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} cpu: [x64] os: [win32] '@vitejs/plugin-react@5.2.0': - resolution: - { - integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: - resolution: - { - integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==, - } - engines: { node: '>=0.4.0' } + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} hasBin: true agent-base@6.0.2: - resolution: - { - integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==, - } - engines: { node: '>= 6.0.0' } + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} ajv@6.15.0: - resolution: - { - integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==, - } + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ansi-styles@4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} argparse@2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} aria-hidden@1.2.6: - resolution: - { - integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} aria-query@5.3.2: - resolution: - { - integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} array-buffer-byte-length@1.0.2: - resolution: - { - integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} array-includes@3.1.9: - resolution: - { - integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} array.prototype.findlast@1.2.5: - resolution: - { - integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} array.prototype.findlastindex@1.2.6: - resolution: - { - integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} array.prototype.flat@1.3.3: - resolution: - { - integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} array.prototype.flatmap@1.3.3: - resolution: - { - integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} array.prototype.tosorted@1.1.4: - resolution: - { - integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} arraybuffer.prototype.slice@1.0.4: - resolution: - { - integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} ast-types-flow@0.0.8: - resolution: - { - integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==, - } + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} async-function@1.0.0: - resolution: - { - integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} asynckit@0.4.0: - resolution: - { - integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, - } + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} autoprefixer@10.5.0: - resolution: - { - integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==, - } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 available-typed-arrays@1.0.7: - resolution: - { - integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} axe-core@4.11.4: - resolution: - { - integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==, - } - engines: { node: '>=4' } + resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} + engines: {node: '>=4'} axios@1.16.1: - resolution: - { - integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==, - } + resolution: {integrity: sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==} axobject-query@4.1.0: - resolution: - { - integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} balanced-match@4.0.4: - resolution: - { - integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} baseline-browser-mapping@2.10.29: - resolution: - { - integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==, - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + engines: {node: '>=6.0.0'} hasBin: true brace-expansion@1.1.14: - resolution: - { - integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==, - } + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} brace-expansion@5.0.6: - resolution: - { - integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} browserslist@4.28.2: - resolution: - { - integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true call-bind-apply-helpers@1.0.2: - resolution: - { - integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} call-bind@1.0.9: - resolution: - { - integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} call-bound@1.0.4: - resolution: - { - integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} caniuse-lite@1.0.30001792: - resolution: - { - integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==, - } + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} chalk@4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} chalk@5.6.2: - resolution: - { - integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, - } - engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} chokidar@4.0.3: - resolution: - { - integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==, - } - engines: { node: '>= 14.16.0' } + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} client-only@0.0.1: - resolution: - { - integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==, - } + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} clipboard-image@0.1.0: - resolution: - { - integrity: sha512-SWk7FgaXLNFld19peQ/rTe0n97lwR1WbkqxV6JKCAOh7U52AKV/PeMFCyt/8IhBdqyDA8rdyewQMKZqvWT5Akg==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-SWk7FgaXLNFld19peQ/rTe0n97lwR1WbkqxV6JKCAOh7U52AKV/PeMFCyt/8IhBdqyDA8rdyewQMKZqvWT5Akg==} + engines: {node: '>=20'} hasBin: true clipboardy@5.3.1: - resolution: - { - integrity: sha512-fPWgBqpp9ctiOQCkE5yjYGzv11ZU55g6ahEgr3COiio6dXdt1mbchCPXQrSR2Y9sZqfi8L7QD3+UosgXVIuPdg==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-fPWgBqpp9ctiOQCkE5yjYGzv11ZU55g6ahEgr3COiio6dXdt1mbchCPXQrSR2Y9sZqfi8L7QD3+UosgXVIuPdg==} + engines: {node: '>=20'} clsx@2.1.1: - resolution: - { - integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} color-convert@2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: '>=7.0.0' } + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} color-name@1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} combined-stream@1.0.8: - resolution: - { - integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, - } - engines: { node: '>= 0.8' } + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} convert-source-map@2.0.0: - resolution: - { - integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, - } + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie@1.1.1: - resolution: - { - integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} crypto-random-string@4.0.0: - resolution: - { - integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} csstype@3.2.3: - resolution: - { - integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, - } + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} damerau-levenshtein@1.0.8: - resolution: - { - integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==, - } + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} data-view-buffer@1.0.2: - resolution: - { - integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} data-view-byte-length@1.0.2: - resolution: - { - integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} data-view-byte-offset@1.0.1: - resolution: - { - integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} debug@3.2.7: - resolution: - { - integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, - } + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2105,11 +1376,8 @@ packages: optional: true debug@4.4.3: - resolution: - { - integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, - } - engines: { node: '>=6.0' } + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} peerDependencies: supports-color: '*' peerDependenciesMeta: @@ -2117,184 +1385,106 @@ packages: optional: true decimal.js@10.6.0: - resolution: - { - integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==, - } + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} deep-is@0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} define-data-property@1.1.4: - resolution: - { - integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} define-properties@1.2.1: - resolution: - { - integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} delayed-stream@1.0.0: - resolution: - { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, - } - engines: { node: '>=0.4.0' } + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} detect-libc@2.1.2: - resolution: - { - integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} doctrine@2.1.0: - resolution: - { - integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dom-helpers@5.2.1: - resolution: - { - integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==, - } + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dunder-proto@1.0.1: - resolution: - { - integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} electron-to-chromium@1.5.353: - resolution: - { - integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==, - } + resolution: {integrity: sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==} emoji-regex@9.2.2: - resolution: - { - integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, - } + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} enhanced-resolve@5.21.3: - resolution: - { - integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==, - } - engines: { node: '>=10.13.0' } + resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} + engines: {node: '>=10.13.0'} es-abstract@1.24.2: - resolution: - { - integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} es-define-property@1.0.1: - resolution: - { - integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} es-errors@1.3.0: - resolution: - { - integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} es-iterator-helpers@1.3.2: - resolution: - { - integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + engines: {node: '>= 0.4'} es-object-atoms@1.1.1: - resolution: - { - integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: - resolution: - { - integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} es-shim-unscopables@1.1.0: - resolution: - { - integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} es-to-primitive@1.3.0: - resolution: - { - integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} esbuild@0.27.7: - resolution: - { - integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: - resolution: - { - integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} escape-string-regexp@4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} escape-string-regexp@5.0.0: - resolution: - { - integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} eslint-config-prettier@10.1.8: - resolution: - { - integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==, - } + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true peerDependencies: eslint: '>=7.0.0' eslint-import-context@0.1.9: - resolution: - { - integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} peerDependencies: unrs-resolver: ^1.0.0 peerDependenciesMeta: @@ -2302,17 +1492,11 @@ packages: optional: true eslint-import-resolver-node@0.3.10: - resolution: - { - integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==, - } + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} eslint-import-resolver-typescript@4.4.4: - resolution: - { - integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==, - } - engines: { node: ^16.17.0 || >=18.6.0 } + resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} + engines: {node: ^16.17.0 || >=18.6.0} peerDependencies: eslint: '*' eslint-plugin-import: '*' @@ -2324,11 +1508,8 @@ packages: optional: true eslint-module-utils@2.12.1: - resolution: - { - integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==, - } - engines: { node: '>=4' } + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' eslint: '*' @@ -2348,11 +1529,8 @@ packages: optional: true eslint-plugin-import@2.32.0: - resolution: - { - integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==, - } - engines: { node: '>=4' } + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 @@ -2361,20 +1539,14 @@ packages: optional: true eslint-plugin-jsx-a11y@6.10.2: - resolution: - { - integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 eslint-plugin-prettier@5.5.5: - resolution: - { - integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==, - } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' eslint: '>=8.0.0' @@ -2387,44 +1559,29 @@ packages: optional: true eslint-plugin-react-hooks@7.1.1: - resolution: - { - integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 eslint-plugin-react-refresh@0.4.26: - resolution: - { - integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==, - } + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} peerDependencies: eslint: '>=8.40' eslint-plugin-react@7.37.5: - resolution: - { - integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, - } - engines: { node: '>=4' } + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 eslint-plugin-simple-import-sort@13.0.0: - resolution: - { - integrity: sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==, - } + resolution: {integrity: sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==} peerDependencies: eslint: '>=5.0.0' eslint-plugin-unused-imports@4.4.1: - resolution: - { - integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==, - } + resolution: {integrity: sha512-oZGYUz1X3sRMGUB+0cZyK2VcvRX5lm/vB56PgNNcU+7ficUCKm66oZWKUubXWnOuPjQ8PvmXtCViXBMONPe7tQ==} peerDependencies: '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 eslint: ^10.0.0 || ^9.0.0 || ^8.0.0 @@ -2433,39 +1590,24 @@ packages: optional: true eslint-scope@8.4.0: - resolution: - { - integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: - resolution: - { - integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} eslint-visitor-keys@4.2.1: - resolution: - { - integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@5.0.1: - resolution: - { - integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==, - } - engines: { node: ^20.19.0 || ^22.13.0 || >=24 } + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint@9.39.4: - resolution: - { - integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: jiti: '*' @@ -2474,84 +1616,48 @@ packages: optional: true espree@10.4.0: - resolution: - { - integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esquery@1.7.0: - resolution: - { - integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, - } - engines: { node: '>=0.10' } + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} esrecurse@4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} estraverse@5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} esutils@2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} execa@5.1.1: - resolution: - { - integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} execa@9.6.1: - resolution: - { - integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==, - } - engines: { node: ^18.19.0 || >=20.5.0 } + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} fast-deep-equal@3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} fast-diff@1.3.0: - resolution: - { - integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, - } + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, - } + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} fdir@6.5.0: - resolution: - { - integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2559,45 +1665,27 @@ packages: optional: true figures@6.1.0: - resolution: - { - integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} file-entry-cache@8.0.0: - resolution: - { - integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, - } - engines: { node: '>=16.0.0' } + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} find-up@5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} flat-cache@4.0.1: - resolution: - { - integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, - } - engines: { node: '>=16' } + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} flatted@3.4.2: - resolution: - { - integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==, - } + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} follow-redirects@1.16.0: - resolution: - { - integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} peerDependencies: debug: '*' peerDependenciesMeta: @@ -2605,1169 +1693,684 @@ packages: optional: true for-each@0.3.5: - resolution: - { - integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} form-data@4.0.5: - resolution: - { - integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==, - } - engines: { node: '>= 6' } + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} fraction.js@5.3.4: - resolution: - { - integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==, - } + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} fsevents@2.3.3: - resolution: - { - integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] function-bind@1.1.2: - resolution: - { - integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, - } + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} function.prototype.name@1.1.8: - resolution: - { - integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} functions-have-names@1.2.3: - resolution: - { - integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, - } + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} generator-function@2.0.1: - resolution: - { - integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} gensync@1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, - } - engines: { node: '>=6.9.0' } + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} get-intrinsic@1.3.0: - resolution: - { - integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} get-proto@1.0.1: - resolution: - { - integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} get-stream@6.0.1: - resolution: - { - integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} get-stream@9.0.1: - resolution: - { - integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} get-symbol-description@1.1.0: - resolution: - { - integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} get-tsconfig@4.14.0: - resolution: - { - integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==, - } + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: '>=10.13.0' } + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} globals@14.0.0: - resolution: - { - integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} globals@16.5.0: - resolution: - { - integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} globalthis@1.0.4: - resolution: - { - integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} gopd@1.2.0: - resolution: - { - integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, - } + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} has-bigints@1.1.0: - resolution: - { - integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} has-property-descriptors@1.0.2: - resolution: - { - integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, - } + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} has-proto@1.2.0: - resolution: - { - integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} has-symbols@1.1.0: - resolution: - { - integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} has-tostringtag@1.0.2: - resolution: - { - integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} hasown@2.0.3: - resolution: - { - integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + engines: {node: '>= 0.4'} hermes-estree@0.25.1: - resolution: - { - integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==, - } + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} hermes-parser@0.25.1: - resolution: - { - integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==, - } + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} https-proxy-agent@5.0.1: - resolution: - { - integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==, - } - engines: { node: '>= 6' } + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} human-signals@2.1.0: - resolution: - { - integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, - } - engines: { node: '>=10.17.0' } + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} human-signals@8.0.1: - resolution: - { - integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==, - } - engines: { node: '>=18.18.0' } + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} ignore@7.0.5: - resolution: - { - integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: '>=0.8.19' } + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} input-otp@1.4.2: - resolution: - { - integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==, - } + resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==} peerDependencies: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc internal-slot@1.1.0: - resolution: - { - integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} intl-messageformat@10.7.18: - resolution: - { - integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==, - } + resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} is-array-buffer@3.0.5: - resolution: - { - integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} is-async-function@2.1.1: - resolution: - { - integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} is-bigint@1.1.0: - resolution: - { - integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} is-boolean-object@1.2.2: - resolution: - { - integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} is-bun-module@2.0.0: - resolution: - { - integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==, - } + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} is-callable@1.2.7: - resolution: - { - integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} is-core-module@2.16.2: - resolution: - { - integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} is-data-view@1.0.2: - resolution: - { - integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} is-date-object@1.1.0: - resolution: - { - integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} is-docker@3.0.0: - resolution: - { - integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} is-finalizationregistry@1.1.1: - resolution: - { - integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} is-generator-function@1.1.2: - resolution: - { - integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-inside-container@1.0.0: - resolution: - { - integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==, - } - engines: { node: '>=14.16' } + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} hasBin: true is-map@2.0.3: - resolution: - { - integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} is-negative-zero@2.0.3: - resolution: - { - integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} is-number-object@1.1.1: - resolution: - { - integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} is-plain-obj@4.1.0: - resolution: - { - integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} is-regex@1.2.1: - resolution: - { - integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} is-set@2.0.3: - resolution: - { - integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} is-shared-array-buffer@1.0.4: - resolution: - { - integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} is-stream@2.0.1: - resolution: - { - integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} is-stream@4.0.1: - resolution: - { - integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} is-string@1.1.1: - resolution: - { - integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} is-symbol@1.1.1: - resolution: - { - integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} is-typed-array@1.1.15: - resolution: - { - integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} is-unicode-supported@2.1.0: - resolution: - { - integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} is-wayland@0.1.0: - resolution: - { - integrity: sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA==} + engines: {node: '>=20'} is-weakmap@2.0.2: - resolution: - { - integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} is-weakref@1.1.1: - resolution: - { - integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} is-weakset@2.0.4: - resolution: - { - integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} is-wsl@3.1.1: - resolution: - { - integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==, - } - engines: { node: '>=16' } + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} is64bit@2.0.0: - resolution: - { - integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} + engines: {node: '>=18'} isarray@2.0.5: - resolution: - { - integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, - } + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} iterator.prototype@1.1.5: - resolution: - { - integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} jiti@2.7.0: - resolution: - { - integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==, - } + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true js-tokens@4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} js-yaml@4.1.1: - resolution: - { - integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, - } + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsesc@3.1.0: - resolution: - { - integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: - resolution: - { - integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, - } + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} json-schema-traverse@0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, - } + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} json5@1.0.2: - resolution: - { - integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==, - } + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true json5@2.2.3: - resolution: - { - integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} hasBin: true jsx-ast-utils@3.3.5: - resolution: - { - integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, - } - engines: { node: '>=4.0' } + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} keyv@4.5.4: - resolution: - { - integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, - } + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} language-subtag-registry@0.3.23: - resolution: - { - integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==, - } + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} language-tags@1.0.9: - resolution: - { - integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==, - } - engines: { node: '>=0.10' } + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} levn@0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} lightningcss-android-arm64@1.32.0: - resolution: - { - integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] lightningcss-darwin-arm64@1.32.0: - resolution: - { - integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] lightningcss-darwin-x64@1.32.0: - resolution: - { - integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] lightningcss-freebsd-x64@1.32.0: - resolution: - { - integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: - { - integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] lightningcss-linux-arm64-gnu@1.32.0: - resolution: - { - integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: - resolution: - { - integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: - resolution: - { - integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: - resolution: - { - integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: - resolution: - { - integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] lightningcss-win32-x64-msvc@1.32.0: - resolution: - { - integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] lightningcss@1.32.0: - resolution: - { - integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==, - } - engines: { node: '>= 12.0.0' } + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} locate-path@6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} lodash.merge@4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} loose-envify@1.4.0: - resolution: - { - integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, - } + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true lru-cache@5.1.1: - resolution: - { - integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, - } + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} lwk_wasm@file:../lwk_wasm/pkg_web: - resolution: { directory: ../lwk_wasm/pkg_web, type: directory } + resolution: {directory: ../lwk_wasm/pkg_web, type: directory} macos-version@6.0.0: - resolution: - { - integrity: sha512-O2S8voA+pMfCHhBn/TIYDXzJ1qNHpPDU32oFxglKnVdJABiYYITt45oLkV9yhwA3E2FDwn3tQqUFrTsr1p3sBQ==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-O2S8voA+pMfCHhBn/TIYDXzJ1qNHpPDU32oFxglKnVdJABiYYITt45oLkV9yhwA3E2FDwn3tQqUFrTsr1p3sBQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} magic-string@0.30.21: - resolution: - { - integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, - } + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} math-intrinsics@1.1.0: - resolution: - { - integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} merge-stream@2.0.0: - resolution: - { - integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, - } + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} mime-db@1.52.0: - resolution: - { - integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, - } - engines: { node: '>= 0.6' } + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} mime-types@2.1.35: - resolution: - { - integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, - } - engines: { node: '>= 0.6' } + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} mimic-fn@2.1.0: - resolution: - { - integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} minimatch@10.2.5: - resolution: - { - integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==, - } - engines: { node: 18 || 20 || >=22 } + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} minimatch@3.1.5: - resolution: - { - integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==, - } + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} minimist@1.2.8: - resolution: - { - integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==, - } + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.12: - resolution: - { - integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true napi-postinstall@0.3.4: - resolution: - { - integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} node-exports-info@1.6.0: - resolution: - { - integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} node-releases@2.0.44: - resolution: - { - integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==, - } + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} npm-run-path@4.0.1: - resolution: - { - integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} npm-run-path@6.0.0: - resolution: - { - integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} object-assign@4.1.1: - resolution: - { - integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} object-inspect@1.13.4: - resolution: - { - integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} object-keys@1.1.1: - resolution: - { - integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} object.assign@4.1.7: - resolution: - { - integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} object.entries@1.1.9: - resolution: - { - integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} object.fromentries@2.0.8: - resolution: - { - integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} object.groupby@1.0.3: - resolution: - { - integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} object.values@1.2.1: - resolution: - { - integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} onetime@5.1.2: - resolution: - { - integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} optionator@0.9.4: - resolution: - { - integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} own-keys@1.0.1: - resolution: - { - integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} p-locate@5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parse-ms@4.0.0: - resolution: - { - integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} path-key@4.0.0: - resolution: - { - integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} path-parse@1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@4.0.4: - resolution: - { - integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} possible-typed-array-names@1.1.0: - resolution: - { - integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} postcss-value-parser@4.2.0: - resolution: - { - integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==, - } + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} postcss@8.5.14: - resolution: - { - integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==, - } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} powershell-utils@0.2.0: - resolution: - { - integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==, - } - engines: { node: '>=20' } + resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==} + engines: {node: '>=20'} prelude-ls@1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} prettier-linter-helpers@1.0.1: - resolution: - { - integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==, - } - engines: { node: '>=6.0.0' } + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} + engines: {node: '>=6.0.0'} prettier@3.8.3: - resolution: - { - integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==, - } - engines: { node: '>=14' } + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} hasBin: true pretty-ms@9.3.0: - resolution: - { - integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} prop-types@15.8.1: - resolution: - { - integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, - } + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} proper-lockfile@4.1.2: - resolution: - { - integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==, - } + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} proxy-from-env@2.1.0: - resolution: - { - integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} punycode@2.3.1: - resolution: - { - integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} react-aria-components@1.17.0: - resolution: - { - integrity: sha512-0EyisMgvsFJ2aML3crDYv2tW5vT2Ryf8PGzY/g63JjDdCbLshlwazhS8JNtPF1vkTkungJJ6sVJbKyX+YKSoFA==, - } + resolution: {integrity: sha512-0EyisMgvsFJ2aML3crDYv2tW5vT2Ryf8PGzY/g63JjDdCbLshlwazhS8JNtPF1vkTkungJJ6sVJbKyX+YKSoFA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-aria@3.48.0: - resolution: - { - integrity: sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==, - } + resolution: {integrity: sha512-jQjd4rBEIMqecBaAKYJbVGK6EqIHLa5znVQ7jwFyK5vCyljoj6KhgtiahmcIPsG5vG5vEDLw+ba+bEWn6A2P4w==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-dom@19.2.6: - resolution: - { - integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==, - } + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: react: ^19.2.6 + react-hook-form@7.77.0: + resolution: {integrity: sha512-Sslh9YDYc0GDlWT/lxasnIduNo4v3yyvqRGvmGKUre5AFjDs/HV9/OafHGD8d+sB2yoL4UIL9L8X9i0WlZZebg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + react-is@16.13.1: - resolution: - { - integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, - } + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-refresh@0.18.0: - resolution: - { - integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} + engines: {node: '>=0.10.0'} react-router-dom@7.15.0: - resolution: - { - integrity: sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==, - } - engines: { node: '>=20.0.0' } + resolution: {integrity: sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==} + engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' react-router@7.15.0: - resolution: - { - integrity: sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==, - } - engines: { node: '>=20.0.0' } + resolution: {integrity: sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==} + engines: {node: '>=20.0.0'} peerDependencies: react: '>=18' react-dom: '>=18' @@ -3776,354 +2379,204 @@ packages: optional: true react-stately@3.46.0: - resolution: - { - integrity: sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==, - } + resolution: {integrity: sha512-OdxhWvHgs2L4OJGIs7hnuTr5WjjMM6enhNEAMRqiekhF8+ITvA2LRwNftOZwcogaoCslGYq5S2VQTQwnm0GbCA==} peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 react-transition-group@4.4.5: - resolution: - { - integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==, - } + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: react: '>=16.6.0' react-dom: '>=16.6.0' react@19.2.6: - resolution: - { - integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} readdirp@4.1.2: - resolution: - { - integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, - } - engines: { node: '>= 14.18.0' } + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} reflect.getprototypeof@1.0.10: - resolution: - { - integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} regexp.prototype.flags@1.5.4: - resolution: - { - integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: '>=4' } + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve-pkg-maps@1.0.0: - resolution: - { - integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, - } + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} resolve@2.0.0-next.6: - resolution: - { - integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + engines: {node: '>= 0.4'} hasBin: true retry@0.12.0: - resolution: - { - integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==, - } - engines: { node: '>= 4' } + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} rollup@4.60.3: - resolution: - { - integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==, - } - engines: { node: '>=18.0.0', npm: '>=8.0.0' } + resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-jxa@3.0.0: - resolution: - { - integrity: sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} safe-array-concat@1.1.4: - resolution: - { - integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==, - } - engines: { node: '>=0.4' } + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} safe-push-apply@1.0.0: - resolution: - { - integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} safe-regex-test@1.1.0: - resolution: - { - integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} scheduler@0.27.0: - resolution: - { - integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==, - } + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} semver@6.3.1: - resolution: - { - integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, - } + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true semver@7.8.0: - resolution: - { - integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} hasBin: true set-cookie-parser@2.7.2: - resolution: - { - integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==, - } + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} set-function-length@1.2.2: - resolution: - { - integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} set-function-name@2.0.2: - resolution: - { - integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} set-proto@1.0.0: - resolution: - { - integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} side-channel-list@1.0.1: - resolution: - { - integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} side-channel-map@1.0.1: - resolution: - { - integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} side-channel-weakmap@1.0.2: - resolution: - { - integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} side-channel@1.1.0: - resolution: - { - integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} signal-exit@3.0.7: - resolution: - { - integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, - } + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} signal-exit@4.1.0: - resolution: - { - integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, - } - engines: { node: '>=14' } + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} stable-hash-x@0.2.0: - resolution: - { - integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} + engines: {node: '>=12.0.0'} stop-iteration-iterator@1.1.0: - resolution: - { - integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} string.prototype.includes@2.0.1: - resolution: - { - integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} string.prototype.matchall@4.0.12: - resolution: - { - integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: - resolution: - { - integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==, - } + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} string.prototype.trim@1.2.10: - resolution: - { - integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} string.prototype.trimend@1.0.9: - resolution: - { - integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: - resolution: - { - integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} strip-bom@3.0.0: - resolution: - { - integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==, - } - engines: { node: '>=4' } + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} strip-final-newline@2.0.0: - resolution: - { - integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} strip-final-newline@4.0.0: - resolution: - { - integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} strip-json-comments@3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} subsume@4.0.0: - resolution: - { - integrity: sha512-BWnYJElmHbYZ/zKevy+TG+SsyoFCmRPDHJbR1MzLxkPOv1Jp/4hGhVUtP98s+wZBsBsHwCXvPTP0x287/WMjGg==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + resolution: {integrity: sha512-BWnYJElmHbYZ/zKevy+TG+SsyoFCmRPDHJbR1MzLxkPOv1Jp/4hGhVUtP98s+wZBsBsHwCXvPTP0x287/WMjGg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: '>=8' } + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} supports-preserve-symlinks-flag@1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} synckit@0.11.12: - resolution: - { - integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==, - } - engines: { node: ^14.18.0 || >=16.0.0 } + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + engines: {node: ^14.18.0 || >=16.0.0} system-architecture@0.1.0: - resolution: - { - integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} + engines: {node: '>=18'} tailwind-merge@3.4.0: - resolution: - { - integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==, - } + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} tailwind-variants@3.2.2: - resolution: - { - integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==, - } - engines: { node: '>=16.x', pnpm: '>=7.x' } + resolution: {integrity: sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg==} + engines: {node: '>=16.x', pnpm: '>=7.x'} peerDependencies: tailwind-merge: '>=3.0.0' tailwindcss: '*' @@ -4132,195 +2585,114 @@ packages: optional: true tailwindcss@4.3.0: - resolution: - { - integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==, - } + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} tapable@2.3.3: - resolution: - { - integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==, - } - engines: { node: '>=6' } + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} + engines: {node: '>=6'} tiny-invariant@1.3.3: - resolution: - { - integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==, - } + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} tinyglobby@0.2.16: - resolution: - { - integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==, - } - engines: { node: '>=12.0.0' } + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} ts-api-utils@2.5.0: - resolution: - { - integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==, - } - engines: { node: '>=18.12' } + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' tsconfig-paths@3.15.0: - resolution: - { - integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==, - } + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tsx@4.21.0: - resolution: - { - integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==, - } - engines: { node: '>=18.0.0' } + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} hasBin: true tw-animate-css@1.4.0: - resolution: - { - integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==, - } + resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} type-check@0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: '>= 0.8.0' } + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} type-fest@1.4.0: - resolution: - { - integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} type-fest@2.19.0: - resolution: - { - integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==, - } - engines: { node: '>=12.20' } + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} typed-array-buffer@1.0.3: - resolution: - { - integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} typed-array-byte-length@1.0.3: - resolution: - { - integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} typed-array-byte-offset@1.0.4: - resolution: - { - integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} typed-array-length@1.0.7: - resolution: - { - integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} typescript-eslint@8.59.3: - resolution: - { - integrity: sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + resolution: {integrity: sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' typescript@5.9.3: - resolution: - { - integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, - } - engines: { node: '>=14.17' } + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} hasBin: true unbox-primitive@1.1.0: - resolution: - { - integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} undici-types@7.16.0: - resolution: - { - integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==, - } + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} unicorn-magic@0.3.0: - resolution: - { - integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} unique-string@3.0.0: - resolution: - { - integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==, - } - engines: { node: '>=12' } + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} unrs-resolver@1.11.1: - resolution: - { - integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==, - } + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} update-browserslist-db@1.2.3: - resolution: - { - integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, - } + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' uri-js@4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} use-sync-external-store@1.6.0: - resolution: - { - integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==, - } + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 vite-plugin-checker@0.13.0: - resolution: - { - integrity: sha512-14EkOZmfinVZNxRmg2uCNDwtqGc/33lU/UEJansHgu27+ad+r6mMBf1Xtnq57jGZWiO/xzwtiEKPYsganw7ZFQ==, - } - engines: { node: '>=16.11' } + resolution: {integrity: sha512-14EkOZmfinVZNxRmg2uCNDwtqGc/33lU/UEJansHgu27+ad+r6mMBf1Xtnq57jGZWiO/xzwtiEKPYsganw7ZFQ==} + engines: {node: '>=16.11'} peerDependencies: '@biomejs/biome': '>=1.7' eslint: '>=9.39.4' @@ -4356,11 +2728,8 @@ packages: optional: true vite@7.3.3: - resolution: - { - integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==, - } - engines: { node: ^20.19.0 || >=22.12.0 } + resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 @@ -4399,90 +2768,55 @@ packages: optional: true vscode-uri@3.1.0: - resolution: - { - integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==, - } + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} which-boxed-primitive@1.1.1: - resolution: - { - integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} which-builtin-type@1.2.1: - resolution: - { - integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} which-collection@1.0.2: - resolution: - { - integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} which-typed-array@1.1.20: - resolution: - { - integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==, - } - engines: { node: '>= 0.4' } + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: '>= 8' } + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true word-wrap@1.2.5: - resolution: - { - integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, - } - engines: { node: '>=0.10.0' } + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} yallist@3.1.1: - resolution: - { - integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, - } + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: '>=10' } + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} yoctocolors@2.1.2: - resolution: - { - integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==, - } - engines: { node: '>=18' } + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} zod-validation-error@4.0.2: - resolution: - { - integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==, - } - engines: { node: '>=18.0.0' } + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} peerDependencies: zod: ^3.25.0 || ^4.0.0 zod@3.25.76: - resolution: - { - integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==, - } + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: + '@adobe/react-spectrum-ui@1.2.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: react: 19.2.6 @@ -6757,6 +5091,10 @@ snapshots: react: 19.2.6 scheduler: 0.27.0 + react-hook-form@7.77.0(react@19.2.6): + dependencies: + react: 19.2.6 + react-is@16.13.1: {} react-refresh@0.18.0: {} diff --git a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx b/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx index 8231a42..6f35096 100644 --- a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx +++ b/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx @@ -13,9 +13,10 @@ import { TxBuilder, TxOutSecrets, type WalletTxOut, - type XOnlyPublicKey, } from 'lwk_web' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { type ComponentProps, useCallback, useEffect, useMemo, useState } from 'react' +import { Controller, type Resolver, useForm } from 'react-hook-form' +import { z as zod } from 'zod' import { broadcastTx, fetchLatestBlockHeight, fetchTxRaw } from '@/api/esplora/methods' import { getTxExplorerUrl } from '@/api/esplora/utils' @@ -38,17 +39,72 @@ import { useTxConfirmations } from '@/simplicity/script-auth/helpers' import { loadScriptAuthProgram } from '@/simplicity/script-auth/program' import { bytesToHex, hexToBytes, isHexStringOfByteLength, normalizeHex } from '@/utils/hex' -interface CreateOfferForm { - factoryAuthOutpoint: string - issuanceFactoryOutpoint: string - factoryAssetId: string - collateralOutpoint: string - collateralAmount: string - principalAssetId: string - principalAmount: string - principalInterestRate: string - loanDurationBlocks: string - protocolFeeKeeperAssetId: string +const integerStringSchema = (label: string) => + zod.string().trim().regex(/^\d+$/, `${label} must be an integer`) + +const bigintStringSchema = (label: string) => + integerStringSchema(label).transform(value => BigInt(value)) + +const numberStringSchema = (label: string) => + integerStringSchema(label).transform(value => Number.parseInt(value, 10)) + +const assetIdStringSchema = (label: string) => + zod + .string() + .transform(normalizeHex) + .refine(value => isHexStringOfByteLength(value, 32), { + message: `${label} must be a 32-byte hex asset id`, + }) + +const outpointStringSchema = (label: string) => + zod + .string() + .trim() + .regex(/^[0-9a-fA-F]{64}:\d+$/, `${label} must be formatted as txid:vout`) + +const createOfferFormSchema = zod.object({ + factoryAuthOutpoint: outpointStringSchema('FactoryAuth outpoint'), + issuanceFactoryOutpoint: outpointStringSchema('IssuanceFactory covenant outpoint'), + factoryAssetId: assetIdStringSchema('Factory asset id'), + collateralOutpoint: outpointStringSchema('Collateral outpoint'), + collateralAmount: bigintStringSchema('Collateral amount'), + principalAssetId: assetIdStringSchema('Principal asset id'), + principalAmount: bigintStringSchema('Principal amount'), + principalInterestRate: numberStringSchema('Interest rate bps'), + loanDurationBlocks: numberStringSchema('Loan duration blocks'), + protocolFeeKeeperAssetId: assetIdStringSchema('Protocol fee keeper asset id'), +}) + +type CreateOfferForm = zod.input +type ParsedCreateOfferForm = zod.output +type CreateOfferFormField = keyof CreateOfferForm +type CreateOfferTextFieldProps = Omit< + ComponentProps, + 'errorMessage' | 'isInvalid' | 'onChange' | 'value' +> & { + name: CreateOfferFormField +} + +const createOfferFormResolver: Resolver = async values => { + const result = createOfferFormSchema.safeParse(values) + if (result.success) { + return { values, errors: {} } + } + + return { + values: {}, + errors: Object.fromEntries( + result.error.issues + .filter(issue => typeof issue.path[0] === 'string') + .map(issue => [ + issue.path[0], + { + type: issue.code, + message: issue.message, + }, + ]), + ), + } } interface CreateOfferSummary { @@ -92,8 +148,8 @@ const INITIAL_STATE: BroadcastState = { } const EMPTY_FORM: CreateOfferForm = { - factoryAuthOutpoint: '5ecc77af31963bfef85418a2b196fc274626b1e0094eb280d37a908f0171a13a:0', - issuanceFactoryOutpoint: '5ecc77af31963bfef85418a2b196fc274626b1e0094eb280d37a908f0171a13a:1', + factoryAuthOutpoint: '6ad27ff9c22819f98a8f08cf777d370c0b30549bee6f8cd86c3522ab203b5018:0', + issuanceFactoryOutpoint: '6ad27ff9c22819f98a8f08cf777d370c0b30549bee6f8cd86c3522ab203b5018:1', factoryAssetId: 'a61ab9c860e382039cb5df9386319887c1a3e60116f5fcb7ad3497b430806d18', collateralOutpoint: '', collateralAmount: DEFAULT_COLLATERAL_AMOUNT, @@ -115,8 +171,11 @@ export default function CreateOfferDemo() { signPset, syncWallet, } = useWallet() - const [form, setForm] = useState(EMPTY_FORM) - const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) + const { control, handleSubmit } = useForm({ + defaultValues: EMPTY_FORM, + mode: 'onSubmit', + resolver: createOfferFormResolver, + }) const [state, setState] = useState({ ...INITIAL_STATE }) const [walletUtxos, setWalletUtxos] = useState([]) const [walletUtxosState, setWalletUtxosState] = useState({ @@ -152,24 +211,26 @@ export default function CreateOfferDemo() { }, [connectionStatus, getWalletUtxos, syncWallet]) useEffect(() => { - void refreshWalletUtxos() - }, [refreshWalletUtxos]) + const timeoutId = setTimeout(() => { + void refreshWalletUtxos() + }, 0) - const setField = (field: keyof CreateOfferForm, value: string) => { - setForm(current => ({ ...current, [field]: value })) - } + return () => clearTimeout(timeoutId) + }, [refreshWalletUtxos]) - const createOffer = async () => { + const createOffer = async (formValues: CreateOfferForm) => { setState(current => ({ ...current, busy: true, error: null, summary: null, txid: null })) let createStage = 'initializing' try { + createStage = 'parse and validate form' + const parsedForm = parseCreateOfferForm(formValues) + createStage = 'get x-only public key' const key = await getXOnlyPublicKey() if (!key) { throw new Error('Missing x-only public key') } - setXOnlyPublicKey(key) createStage = 'get receive address' const receiveAddressString = await getReceiveAddress() @@ -187,20 +248,17 @@ export default function CreateOfferDemo() { await syncWallet() const walletUtxos = await getWalletUtxos() setWalletUtxos(walletUtxos) - logWalletUtxoLookup(walletUtxos, [ - { label: 'FactoryAuth', outpoint: form.factoryAuthOutpoint }, - { label: 'Collateral', outpoint: form.collateralOutpoint }, - ]) - const factoryAuthUtxo = findUtxoByOutpoint(walletUtxos, form.factoryAuthOutpoint.trim()) - const collateralUtxo = requireWalletUtxo(walletUtxos, form.collateralOutpoint, 'Collateral') - - createStage = 'parse and validate form' - const factoryAsset = parseAssetId(form.factoryAssetId, 'Factory asset id') - const principalAsset = parseAssetId(form.principalAssetId, 'Principal asset id') - const protocolFeeKeeperAsset = parseAssetId( - form.protocolFeeKeeperAssetId, - 'Protocol fee keeper asset id', + const factoryAuthUtxo = findUtxoByOutpoint(walletUtxos, parsedForm.factoryAuthOutpoint) + const collateralUtxo = requireWalletUtxo( + walletUtxos, + parsedForm.collateralOutpoint, + 'Collateral', ) + + createStage = 'prepare validated form values' + const factoryAsset = parseAssetId(parsedForm.factoryAssetId) + const principalAsset = parseAssetId(parsedForm.principalAssetId) + const protocolFeeKeeperAsset = parseAssetId(parsedForm.protocolFeeKeeperAssetId) const policyAsset = lwkNetwork.policyAsset() const factoryAssetString = factoryAsset.toString() const principalAssetString = principalAsset.toString() @@ -214,23 +272,17 @@ export default function CreateOfferDemo() { NFT_AMOUNT, 'FactoryAuth', ) - } else { - console.info('[CreateOfferDemo] FactoryAuth not in wallet UTXO set; using external UTXO', { - outpoint: form.factoryAuthOutpoint.trim(), - asset: factoryAssetString, - amount: NFT_AMOUNT.toString(), - }) } assertWalletUtxoAssetAndAmount( collateralUtxo, policyAssetString, - parseBigint(form.collateralAmount, 'Collateral amount'), + parsedForm.collateralAmount, 'Collateral', ) - const factoryAuthOutpointString = form.factoryAuthOutpoint.trim() - const issuanceFactoryOutpointString = form.issuanceFactoryOutpoint.trim() - const collateralOutpointString = form.collateralOutpoint.trim() + const factoryAuthOutpointString = parsedForm.factoryAuthOutpoint + const issuanceFactoryOutpointString = parsedForm.issuanceFactoryOutpoint + const collateralOutpointString = parsedForm.collateralOutpoint const issuanceFactoryOutpoint = parseOutPoint(issuanceFactoryOutpointString) const collateralOutpoint = parseOutPoint(collateralOutpointString) @@ -244,9 +296,6 @@ export default function CreateOfferDemo() { }) const issuanceFactoryAddress = issuanceFactoryProgram.createP2trAddress(key, lwkNetwork) const issuanceFactoryAddressString = issuanceFactoryAddress.toString() - const issuanceFactoryScriptPubkeyHex = bytesToHex( - issuanceFactoryAddress.scriptPubkey().bytes(), - ) const issuanceFactoryTx = Transaction.fromBytes( await fetchTxRaw(issuanceFactoryOutpoint.txid().toString()), @@ -259,7 +308,7 @@ export default function CreateOfferDemo() { const issuanceFactoryExternalUtxo = new ExternalUtxo( issuanceFactoryOutpoint.vout(), issuanceFactoryTx, - TxOutSecrets.fromExplicit(parseAssetId(factoryAssetString, 'Factory asset id'), NFT_AMOUNT), + TxOutSecrets.fromExplicit(parseAssetId(factoryAssetString), NFT_AMOUNT), DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, true, ) @@ -277,28 +326,22 @@ export default function CreateOfferDemo() { const borrowerNftAssetString = borrowerNftAsset.toString() const lenderNftAssetString = lenderNftAsset.toString() const currentBlockHeight = await fetchLatestBlockHeight() - const loanDurationBlocks = parseNumber(form.loanDurationBlocks, 'Loan duration blocks') + const loanDurationBlocks = parsedForm.loanDurationBlocks const offerParameters = { - collateralAmount: parseBigint(form.collateralAmount, 'Collateral amount'), - principalAmount: parseBigint(form.principalAmount, 'Principal amount'), - principalInterestRate: parseNumber(form.principalInterestRate, 'Interest rate bps'), + collateralAmount: parsedForm.collateralAmount, + principalAmount: parsedForm.principalAmount, + principalInterestRate: parsedForm.principalInterestRate, loanExpirationTime: currentBlockHeight + loanDurationBlocks, } createStage = 'compile lending and ScriptAuth programs' const derivedLendingParams = buildDerivedLendingOfferProgramParams( { - collateralAssetId: parseAssetId(policyAssetString, 'Policy asset id').toBytes(), - principalAssetId: parseAssetId(principalAssetString, 'Principal asset id').toBytes(), - borrowerNftAssetId: parseAssetId( - borrowerNftAssetString, - 'Borrower NFT asset id', - ).toBytes(), - lenderNftAssetId: parseAssetId(lenderNftAssetString, 'Lender NFT asset id').toBytes(), - protocolFeeKeeperAssetId: parseAssetId( - protocolFeeKeeperAssetString, - 'Protocol fee keeper asset id', - ).toBytes(), + collateralAssetId: parseAssetId(policyAssetString).toBytes(), + principalAssetId: parseAssetId(principalAssetString).toBytes(), + borrowerNftAssetId: parseAssetId(borrowerNftAssetString).toBytes(), + lenderNftAssetId: parseAssetId(lenderNftAssetString).toBytes(), + protocolFeeKeeperAssetId: parseAssetId(protocolFeeKeeperAssetString).toBytes(), offerParameters, }, key, @@ -316,31 +359,17 @@ export default function CreateOfferDemo() { ) const lenderNftScriptAuthAddressString = lenderNftScriptAuthAddress.toString() const metadata = await buildPendingOfferMetadata({ - principalAssetId: parseAssetId(principalAssetString, 'Principal asset id').toBytes(), + principalAssetId: parseAssetId(principalAssetString).toBytes(), offerParameters, }) - logAddressShape('receive explicit', receiveAddressExplicitString) - logAddressShape('receive confidential', receiveAddressString) - logAddressShape('issuanceFactory covenant', issuanceFactoryAddressString) - logAddressShape('lender NFT ScriptAuth', lenderNftScriptAuthAddressString) - createStage = 'TxBuilder.new' - console.info('[CreateOfferDemo] stage', createStage) let txBuilder = new TxBuilder(lwkNetwork) createStage = 'TxBuilder.feeRate' - console.info('[CreateOfferDemo] stage', createStage, { feeRate: DEFAULT_FEE_RATE }) txBuilder = txBuilder.feeRate(DEFAULT_FEE_RATE) createStage = 'TxBuilder.setInputOrder' - console.info('[CreateOfferDemo] stage', createStage, { - inputOrder: [ - factoryAuthOutpointString, - issuanceFactoryOutpointString, - collateralOutpointString, - ], - }) txBuilder = txBuilder.setInputOrder([ parseOutPoint(factoryAuthOutpointString), parseOutPoint(issuanceFactoryOutpointString), @@ -352,43 +381,23 @@ export default function CreateOfferDemo() { : [issuanceFactoryExternalUtxo] createStage = 'TxBuilder.addExternalUtxos covenant/explicit inputs' - console.info('[CreateOfferDemo] stage', createStage, { - externalOutpoints: factoryAuthExternalUtxo - ? [factoryAuthOutpointString, issuanceFactoryOutpointString] - : [issuanceFactoryOutpointString], - }) txBuilder = txBuilder.addExternalUtxos(externalUtxos) createStage = 'TxBuilder.addExplicitRecipient FactoryAuth back to user' - console.info('[CreateOfferDemo] stage', createStage, { - address: receiveAddressExplicitString, - asset: factoryAssetString, - amount: NFT_AMOUNT.toString(), - }) txBuilder = txBuilder.addExplicitRecipient( parseAddress(receiveAddressExplicitString, lwkNetwork), NFT_AMOUNT, - parseAssetId(factoryAssetString, 'Factory asset id'), + parseAssetId(factoryAssetString), ) - createStage = 'TxBuilder.addExplicitScriptOutput IssuanceFactory covenant' - console.info('[CreateOfferDemo] stage', createStage, { - scriptPubkeyHex: issuanceFactoryScriptPubkeyHex, - asset: factoryAssetString, - amount: NFT_AMOUNT.toString(), - }) - txBuilder = txBuilder.addExplicitScriptOutput( - new Script(issuanceFactoryScriptPubkeyHex), + createStage = 'TxBuilder.addExplicitRecipient IssuanceFactory covenant' + txBuilder = txBuilder.addExplicitRecipient( + parseAddress(issuanceFactoryAddressString, lwkNetwork), NFT_AMOUNT, - parseAssetId(factoryAssetString, 'Factory asset id'), + parseAssetId(factoryAssetString), ) createStage = 'TxBuilder.issueAssetToRecipients Borrower NFT to user' - console.info('[CreateOfferDemo] stage', createStage, { - inputOutpoint: issuanceFactoryOutpointString, - address: receiveAddressExplicitString, - amount: NFT_AMOUNT.toString(), - }) txBuilder = txBuilder.issueAssetToRecipients( [ IssuanceRecipient.fromAddress( @@ -403,11 +412,6 @@ export default function CreateOfferDemo() { ) createStage = 'TxBuilder.issueAssetToRecipients Lender NFT to ScriptAuth' - console.info('[CreateOfferDemo] stage', createStage, { - inputOutpoint: collateralOutpointString, - address: lenderNftScriptAuthAddressString, - amount: NFT_AMOUNT.toString(), - }) txBuilder = txBuilder.issueAssetToRecipients( [ IssuanceRecipient.fromAddress( @@ -422,87 +426,27 @@ export default function CreateOfferDemo() { ) createStage = 'TxBuilder.addExplicitScriptOutput metadata OP_RETURN' - console.info('[CreateOfferDemo] stage', createStage, { - asset: policyAssetString, - metadataHex: bytesToHex(metadata), - }) txBuilder = txBuilder.addExplicitScriptOutput( Script.newOpReturn(metadata), 0n, - parseAssetId(policyAssetString, 'Policy asset id'), + parseAssetId(policyAssetString), ) createStage = 'TxBuilder.addExplicitScriptOutput Lending covenant collateral' - console.info('[CreateOfferDemo] stage', createStage, { - scriptPubkeyHex: lendingScriptPubkeyHex, - asset: policyAssetString, - amount: offerParameters.collateralAmount.toString(), - }) txBuilder = txBuilder.addExplicitScriptOutput( new Script(lendingScriptPubkeyHex), offerParameters.collateralAmount, - parseAssetId(policyAssetString, 'Policy asset id'), + parseAssetId(policyAssetString), ) createStage = 'TxBuilder.finish' - console.info('[CreateOfferDemo] stage', createStage) const pset = txBuilder.finish(wollet) - console.log( - pset.outputs().map((output, index) => ({ - index, - asset: output.asset()?.toString?.(), - amount: output.amount()?.toString?.(), - })), - ) - createStage = 'sign offer PSET' const signedPset = await signPset(pset) + createStage = 'finalize wallet inputs' - console.log('[CreateOfferDemo] finalize params', { - currentIndex: 1, - prevOutputs: [ - factoryAuthOutpointString, - issuanceFactoryOutpointString, - collateralOutpointString, - ], - }) const finalizedWalletPset = wollet.finalize(signedPset) - const txWithWalletWitnesses = finalizedWalletPset.extractTx() - - console.log( - txWithWalletWitnesses.outputs.map((output, index) => ({ - index, - script: bytesToHex(output.scriptPubkey().bytes()), - isOpReturn: bytesToHex(output.scriptPubkey().bytes()).startsWith('6a'), - })), - ) - - console.table( - txWithWalletWitnesses.outputs.map((output, index) => ({ - index, - scriptHash: output.scriptPubkey().jet_sha256_hex(), - script: bytesToHex(output.scriptPubkey().bytes()), - })), - ) - - console.table( - txWithWalletWitnesses.inputs.map((input, index) => ({ - index, - outpoint: input.outpoint().toString(), - })), - ) - - console.log(txWithWalletWitnesses.inputs) - - console.table( - txWithWalletWitnesses.inputs.map((input, index) => ({ - index, - outpoint: input.outpoint().toString(), - })), - ) - - console.log(txWithWalletWitnesses.toString()) createStage = 'load previous txouts for Simplicity finalize' const finalizationFactoryAuthOutpoint = parseOutPoint(factoryAuthOutpointString) @@ -524,19 +468,7 @@ export default function CreateOfferDemo() { } createStage = 'finalize IssuanceFactory covenant input' - - console.log('factory txout script hash', issuanceFactoryTxOut.scriptPubkey().jet_sha256_hex()) - - console.log( - 'output 4 script hash', - txWithWalletWitnesses.outputs[4]?.scriptPubkey().jet_sha256_hex(), - ) - - console.log('EXPECTED', { - factoryAssetId: factoryAssetString, - borrowerNftAssetId: borrowerNftAssetString, - lenderNftAssetId: lenderNftAssetString, - }) + const txWithWalletWitnesses = finalizedWalletPset.extractTx() const finalizedTx = issuanceFactoryProgram.finalizeTransaction( txWithWalletWitnesses, @@ -551,17 +483,6 @@ export default function CreateOfferDemo() { SimplicityLogLevel.Trace, ) - const witness = buildIssuanceFactoryWitness({ - branch: 'IssueAssets', - outputIndex: 0, - }) - - console.log('ISSUANCE FACTORY SCRIPT', issuanceFactoryScriptPubkeyHex) - console.log('SCRIPT AUTH SCRIPT', lenderNftScriptAuthAddress.scriptPubkey().toString()) - console.log('LENDING SCRIPT', lendingScriptPubkeyHex) - - console.log('[CreateOfferDemo] IssuanceFactory witness', witness) - createStage = 'broadcast transaction' const txid = await broadcastTx(finalizedTx.toString()) @@ -570,9 +491,9 @@ export default function CreateOfferDemo() { error: null, summary: { inputs: { - '0 FactoryAuth': form.factoryAuthOutpoint, - '1 IssuanceFactory covenant': form.issuanceFactoryOutpoint, - '2 Collateral LBTC': form.collateralOutpoint, + '0 FactoryAuth': parsedForm.factoryAuthOutpoint, + '1 IssuanceFactory covenant': parsedForm.issuanceFactoryOutpoint, + '2 Collateral LBTC': parsedForm.collateralOutpoint, }, outputs: { '0 FactoryAuth back to user': receiveAddressExplicitString, @@ -616,15 +537,32 @@ export default function CreateOfferDemo() { }) } catch (err) { const errorMessage = err instanceof Error ? err.message : String(err) - console.error('[CreateOfferDemo] create failed', { createStage, err }) + const errorBody = + err instanceof Error && 'body' in err && typeof err.body === 'string' ? err.body : null setState(current => ({ ...current, busy: false, - error: `${createStage}: ${errorMessage}`, + error: `${createStage}: ${errorMessage}${errorBody ? ` | response: ${errorBody}` : ''}`, })) } } + const renderTextField = ({ name, ...props }: CreateOfferTextFieldProps) => ( + ( + + )} + /> + ) + return (
Create Offer Demo
@@ -633,68 +571,71 @@ export default function CreateOfferDemo() { and LBTC collateral input. Borrower account UTXOs are entered manually.

-
- setField('factoryAuthOutpoint', value)} - description='Manual fallback for explicit wallet UTXOs that LWK scan does not list' - /> - setField('issuanceFactoryOutpoint', value)} - /> - setField('factoryAssetId', value)} - /> - setField('collateralOutpoint', key ? String(key) : '')} - description={ - collateralUtxoOptions.length - ? `${collateralUtxoOptions.length} wallet LBTC UTXO(s)` - : 'No wallet LBTC UTXOs loaded' - } - /> - setField('collateralAmount', value)} - /> - setField('principalAmount', value)} - /> - setField('principalAssetId', value)} - /> - setField('protocolFeeKeeperAssetId', value)} - /> - setField('principalInterestRate', value)} - /> - setField('loanDurationBlocks', value)} - /> +
+
+ {renderTextField({ + name: 'factoryAuthOutpoint', + label: 'FactoryAuth outpoint', + placeholder: 'txid:0', + description: 'Manual fallback for explicit wallet UTXOs that LWK scan does not list', + })} + {renderTextField({ + name: 'issuanceFactoryOutpoint', + label: 'IssuanceFactory covenant outpoint', + placeholder: 'txid:1', + })} + {renderTextField({ + name: 'factoryAssetId', + label: 'Factory asset id', + placeholder: '64 hex chars', + })} + ( + field.onChange(key ? String(key) : '')} + description={ + collateralUtxoOptions.length + ? `${collateralUtxoOptions.length} wallet LBTC UTXO(s)` + : 'No wallet LBTC UTXOs loaded' + } + /> + )} + /> +
+ +
+ {renderTextField({ + name: 'collateralAmount', + label: 'Collateral amount', + })} + {renderTextField({ + name: 'principalAmount', + label: 'Principal amount', + })} + {renderTextField({ + name: 'principalAssetId', + label: 'Principal asset id', + })} + {renderTextField({ + name: 'protocolFeeKeeperAssetId', + label: 'Protocol fee keeper asset id', + })} + {renderTextField({ + name: 'principalInterestRate', + label: 'Interest rate bps', + })} + {renderTextField({ + name: 'loanDurationBlocks', + label: 'Loan duration blocks', + })} +
@@ -714,7 +655,12 @@ export default function CreateOfferDemo() { > Refresh LBTC UTXOs - + void handleSubmit(createOffer)()} + > Create Offer
@@ -722,21 +668,6 @@ export default function CreateOfferDemo() { {state.error ?

Create: {state.error}

: null} - -
-        {JSON.stringify(
-          {
-            connectionStatus,
-            hasPubkey: !!xOnlyPublicKey,
-            broadcasting: state.busy,
-            txid: state.txid,
-            confirmations,
-            error: state.error,
-          },
-          null,
-          2,
-        )}
-      
) } @@ -789,63 +720,12 @@ function formatCollateralUtxoOption(utxo: WalletTxOut): UiSelectOption { } } -function logWalletUtxoLookup( - walletUtxos: WalletTxOut[], - targets: { label: string; outpoint: string }[], -) { - console.group('[CreateOfferDemo] wallet UTXO lookup') - console.table( - targets.map(target => ({ - label: target.label, - rawOutpoint: target.outpoint, - trimmedOutpoint: target.outpoint.trim(), - existsInWallet: Boolean(findUtxoByOutpoint(walletUtxos, target.outpoint.trim())), - })), - ) - console.table( - walletUtxos.map(utxo => ({ - outpoint: outpointToString(utxo), - asset: utxo.unblinded().asset().toString(), - amount: utxo.unblinded().value().toString(), - height: utxo.height() ?? 'mempool', - address: utxo.address().toString(), - })), - ) - console.groupEnd() -} - -function logAddressShape(label: string, addressString: string) { - try { - const address = new Address(addressString) - console.info('[CreateOfferDemo] address shape', { - label, - address: addressString, - isBlinded: address.isBlinded(), - isMainnet: address.isMainnet(), - }) - } catch (err) { - console.info('[CreateOfferDemo] address shape parse failed', { - label, - address: addressString, - err, - }) - } -} - function parseOutPoint(value: string): OutPoint { - const trimmed = value.trim() - if (!/^[0-9a-fA-F]{64}:\d+$/.test(trimmed)) { - throw new Error(`Invalid outpoint: ${value}`) - } - return new OutPoint(trimmed) + return new OutPoint(value) } -function parseAssetId(value: string, label: string): AssetId { - const normalized = normalizeHex(value) - if (!isHexStringOfByteLength(normalized, 32)) { - throw new Error(`${label} must be a 32-byte hex asset id`) - } - return AssetId.fromString(normalized) +function parseAssetId(value: string): AssetId { + return AssetId.fromString(value) } function parseAddress(value: string, network: Network): Address { @@ -872,26 +752,18 @@ async function buildExplicitExternalUtxo( return new ExternalUtxo( outpoint.vout(), tx, - TxOutSecrets.fromExplicit(parseAssetId(assetIdString, `${label} asset id`), amount), + TxOutSecrets.fromExplicit(parseAssetId(assetIdString), amount), DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, true, ) } -function parseBigint(value: string, label: string): bigint { - const trimmed = value.trim() - if (!/^\d+$/.test(trimmed)) { - throw new Error(`${label} must be an integer`) - } - return BigInt(trimmed) -} - -function parseNumber(value: string, label: string): number { - const trimmed = value.trim() - if (!/^\d+$/.test(trimmed)) { - throw new Error(`${label} must be an integer`) +function parseCreateOfferForm(form: CreateOfferForm): ParsedCreateOfferForm { + const result = createOfferFormSchema.safeParse(form) + if (!result.success) { + throw new Error(result.error.issues.map(issue => issue.message).join('; ')) } - return Number.parseInt(trimmed, 10) + return result.data } function requireWalletUtxo( From d05a39cb157456c3b802a0829fa44728f8f4ff69 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 5 Jun 2026 15:26:42 +0300 Subject: [PATCH 10/16] refactor(wallet): update address retrieval logic for session --- .../src/pages/Dashboard/CreateOfferDemo.tsx | 225 ++++++++---------- web-v2/src/pages/Dashboard/WalletDemo.tsx | 73 ------ .../src/providers/wallet/WalletProvider.tsx | 2 +- 3 files changed, 99 insertions(+), 201 deletions(-) diff --git a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx b/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx index 6f35096..c39f820 100644 --- a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx +++ b/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx @@ -5,7 +5,6 @@ import { ContractHash, ExternalUtxo, IssuanceRecipient, - type Network, OutPoint, Script, SimplicityLogLevel, @@ -256,9 +255,9 @@ export default function CreateOfferDemo() { ) createStage = 'prepare validated form values' - const factoryAsset = parseAssetId(parsedForm.factoryAssetId) - const principalAsset = parseAssetId(parsedForm.principalAssetId) - const protocolFeeKeeperAsset = parseAssetId(parsedForm.protocolFeeKeeperAssetId) + const factoryAsset = AssetId.fromString(parsedForm.factoryAssetId) + const principalAsset = AssetId.fromString(parsedForm.principalAssetId) + const protocolFeeKeeperAsset = AssetId.fromString(parsedForm.protocolFeeKeeperAssetId) const policyAsset = lwkNetwork.policyAsset() const factoryAssetString = factoryAsset.toString() const principalAssetString = principalAsset.toString() @@ -283,8 +282,8 @@ export default function CreateOfferDemo() { const factoryAuthOutpointString = parsedForm.factoryAuthOutpoint const issuanceFactoryOutpointString = parsedForm.issuanceFactoryOutpoint const collateralOutpointString = parsedForm.collateralOutpoint - const issuanceFactoryOutpoint = parseOutPoint(issuanceFactoryOutpointString) - const collateralOutpoint = parseOutPoint(collateralOutpointString) + const issuanceFactoryOutpoint = new OutPoint(issuanceFactoryOutpointString) + const collateralOutpoint = new OutPoint(collateralOutpointString) createStage = 'prepare addresses and IssuanceFactory external UTXO' const receiveAddressExplicitString = Address.parse(receiveAddressString, lwkNetwork) @@ -308,7 +307,7 @@ export default function CreateOfferDemo() { const issuanceFactoryExternalUtxo = new ExternalUtxo( issuanceFactoryOutpoint.vout(), issuanceFactoryTx, - TxOutSecrets.fromExplicit(parseAssetId(factoryAssetString), NFT_AMOUNT), + TxOutSecrets.fromExplicit(AssetId.fromString(factoryAssetString), NFT_AMOUNT), DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, true, ) @@ -321,8 +320,14 @@ export default function CreateOfferDemo() { 'FactoryAuth', ) - const borrowerNftAsset = assetIdFromIssuance(issuanceFactoryOutpoint, emptyContractHash()) - const lenderNftAsset = assetIdFromIssuance(collateralOutpoint, emptyContractHash()) + const borrowerNftAsset = assetIdFromIssuance( + issuanceFactoryOutpoint, + ContractHash.fromBytes(new Uint8Array(32)), + ) + const lenderNftAsset = assetIdFromIssuance( + collateralOutpoint, + ContractHash.fromBytes(new Uint8Array(32)), + ) const borrowerNftAssetString = borrowerNftAsset.toString() const lenderNftAssetString = lenderNftAsset.toString() const currentBlockHeight = await fetchLatestBlockHeight() @@ -337,11 +342,11 @@ export default function CreateOfferDemo() { createStage = 'compile lending and ScriptAuth programs' const derivedLendingParams = buildDerivedLendingOfferProgramParams( { - collateralAssetId: parseAssetId(policyAssetString).toBytes(), - principalAssetId: parseAssetId(principalAssetString).toBytes(), - borrowerNftAssetId: parseAssetId(borrowerNftAssetString).toBytes(), - lenderNftAssetId: parseAssetId(lenderNftAssetString).toBytes(), - protocolFeeKeeperAssetId: parseAssetId(protocolFeeKeeperAssetString).toBytes(), + collateralAssetId: AssetId.fromString(policyAssetString).toBytes(), + principalAssetId: AssetId.fromString(principalAssetString).toBytes(), + borrowerNftAssetId: AssetId.fromString(borrowerNftAssetString).toBytes(), + lenderNftAssetId: AssetId.fromString(lenderNftAssetString).toBytes(), + protocolFeeKeeperAssetId: AssetId.fromString(protocolFeeKeeperAssetString).toBytes(), offerParameters, }, key, @@ -359,7 +364,7 @@ export default function CreateOfferDemo() { ) const lenderNftScriptAuthAddressString = lenderNftScriptAuthAddress.toString() const metadata = await buildPendingOfferMetadata({ - principalAssetId: parseAssetId(principalAssetString).toBytes(), + principalAssetId: AssetId.fromString(principalAssetString).toBytes(), offerParameters, }) @@ -371,9 +376,9 @@ export default function CreateOfferDemo() { createStage = 'TxBuilder.setInputOrder' txBuilder = txBuilder.setInputOrder([ - parseOutPoint(factoryAuthOutpointString), - parseOutPoint(issuanceFactoryOutpointString), - parseOutPoint(collateralOutpointString), + new OutPoint(factoryAuthOutpointString), + new OutPoint(issuanceFactoryOutpointString), + new OutPoint(collateralOutpointString), ]) const externalUtxos = factoryAuthExternalUtxo @@ -385,58 +390,48 @@ export default function CreateOfferDemo() { createStage = 'TxBuilder.addExplicitRecipient FactoryAuth back to user' txBuilder = txBuilder.addExplicitRecipient( - parseAddress(receiveAddressExplicitString, lwkNetwork), + new Address(receiveAddressExplicitString), // tex1… explicit addresses not supported by Address.parse NFT_AMOUNT, - parseAssetId(factoryAssetString), + AssetId.fromString(factoryAssetString), ) createStage = 'TxBuilder.addExplicitRecipient IssuanceFactory covenant' txBuilder = txBuilder.addExplicitRecipient( - parseAddress(issuanceFactoryAddressString, lwkNetwork), + new Address(issuanceFactoryAddressString), NFT_AMOUNT, - parseAssetId(factoryAssetString), + AssetId.fromString(factoryAssetString), ) createStage = 'TxBuilder.issueAssetToRecipients Borrower NFT to user' txBuilder = txBuilder.issueAssetToRecipients( - [ - IssuanceRecipient.fromAddress( - NFT_AMOUNT, - parseAddress(receiveAddressExplicitString, lwkNetwork), - ), - ], + [IssuanceRecipient.fromAddress(NFT_AMOUNT, new Address(receiveAddressExplicitString))], REISSUANCE_TOKEN_AMOUNT, null, null, - parseOutPoint(issuanceFactoryOutpointString), + new OutPoint(issuanceFactoryOutpointString), ) createStage = 'TxBuilder.issueAssetToRecipients Lender NFT to ScriptAuth' txBuilder = txBuilder.issueAssetToRecipients( - [ - IssuanceRecipient.fromAddress( - NFT_AMOUNT, - parseAddress(lenderNftScriptAuthAddressString, lwkNetwork), - ), - ], + [IssuanceRecipient.fromAddress(NFT_AMOUNT, new Address(lenderNftScriptAuthAddressString))], REISSUANCE_TOKEN_AMOUNT, null, null, - parseOutPoint(collateralOutpointString), + new OutPoint(collateralOutpointString), ) createStage = 'TxBuilder.addExplicitScriptOutput metadata OP_RETURN' txBuilder = txBuilder.addExplicitScriptOutput( Script.newOpReturn(metadata), 0n, - parseAssetId(policyAssetString), + AssetId.fromString(policyAssetString), ) createStage = 'TxBuilder.addExplicitScriptOutput Lending covenant collateral' txBuilder = txBuilder.addExplicitScriptOutput( new Script(lendingScriptPubkeyHex), offerParameters.collateralAmount, - parseAssetId(policyAssetString), + AssetId.fromString(policyAssetString), ) createStage = 'TxBuilder.finish' @@ -449,8 +444,8 @@ export default function CreateOfferDemo() { const finalizedWalletPset = wollet.finalize(signedPset) createStage = 'load previous txouts for Simplicity finalize' - const finalizationFactoryAuthOutpoint = parseOutPoint(factoryAuthOutpointString) - const finalizationCollateralOutpoint = parseOutPoint(collateralOutpointString) + const finalizationFactoryAuthOutpoint = new OutPoint(factoryAuthOutpointString) + const finalizationCollateralOutpoint = new OutPoint(collateralOutpointString) const factoryAuthTx = Transaction.fromBytes( await fetchTxRaw(finalizationFactoryAuthOutpoint.txid().toString()), ) @@ -571,71 +566,67 @@ export default function CreateOfferDemo() { and LBTC collateral input. Borrower account UTXOs are entered manually.

-
-
- {renderTextField({ - name: 'factoryAuthOutpoint', - label: 'FactoryAuth outpoint', - placeholder: 'txid:0', - description: 'Manual fallback for explicit wallet UTXOs that LWK scan does not list', - })} - {renderTextField({ - name: 'issuanceFactoryOutpoint', - label: 'IssuanceFactory covenant outpoint', - placeholder: 'txid:1', - })} - {renderTextField({ - name: 'factoryAssetId', - label: 'Factory asset id', - placeholder: '64 hex chars', - })} - ( - field.onChange(key ? String(key) : '')} - description={ - collateralUtxoOptions.length - ? `${collateralUtxoOptions.length} wallet LBTC UTXO(s)` - : 'No wallet LBTC UTXOs loaded' - } - /> - )} - /> -
- -
- {renderTextField({ - name: 'collateralAmount', - label: 'Collateral amount', - })} - {renderTextField({ - name: 'principalAmount', - label: 'Principal amount', - })} - {renderTextField({ - name: 'principalAssetId', - label: 'Principal asset id', - })} - {renderTextField({ - name: 'protocolFeeKeeperAssetId', - label: 'Protocol fee keeper asset id', - })} - {renderTextField({ - name: 'principalInterestRate', - label: 'Interest rate bps', - })} - {renderTextField({ - name: 'loanDurationBlocks', - label: 'Loan duration blocks', - })} -
+
+ {renderTextField({ + name: 'factoryAuthOutpoint', + label: 'FactoryAuth outpoint', + placeholder: 'txid:0', + description: 'Manual fallback for explicit wallet UTXOs that LWK scan does not list', + })} + {renderTextField({ + name: 'issuanceFactoryOutpoint', + label: 'IssuanceFactory covenant outpoint', + placeholder: 'txid:1', + })} + {renderTextField({ + name: 'factoryAssetId', + label: 'Factory asset id', + placeholder: '64 hex chars', + })} + ( + field.onChange(key ? String(key) : '')} + description={ + collateralUtxoOptions.length + ? `${collateralUtxoOptions.length} wallet LBTC UTXO(s)` + : 'No wallet LBTC UTXOs loaded' + } + /> + )} + /> + + {renderTextField({ + name: 'collateralAmount', + label: 'Collateral amount', + })} + {renderTextField({ + name: 'principalAmount', + label: 'Principal amount', + })} + {renderTextField({ + name: 'principalAssetId', + label: 'Principal asset id', + })} + {renderTextField({ + name: 'protocolFeeKeeperAssetId', + label: 'Protocol fee keeper asset id', + })} + {renderTextField({ + name: 'principalInterestRate', + label: 'Interest rate bps', + })} + {renderTextField({ + name: 'loanDurationBlocks', + label: 'Loan duration blocks', + })}
@@ -720,29 +711,13 @@ function formatCollateralUtxoOption(utxo: WalletTxOut): UiSelectOption { } } -function parseOutPoint(value: string): OutPoint { - return new OutPoint(value) -} - -function parseAssetId(value: string): AssetId { - return AssetId.fromString(value) -} - -function parseAddress(value: string, network: Network): Address { - // Address.parse currently rejects explicit tex1... addresses in lwk_wasm. - // TxBuilder still needs explicit addresses for addExplicitRecipient, so use - // the constructor here and keep network-specific validation at call sites. - void network - return new Address(value) -} - async function buildExplicitExternalUtxo( outpointString: string, assetIdString: string, amount: bigint, label: string, ): Promise { - const outpoint = parseOutPoint(outpointString) + const outpoint = new OutPoint(outpointString) const tx = Transaction.fromBytes(await fetchTxRaw(outpoint.txid().toString())) const txOut = tx.outputs[outpoint.vout()] if (!txOut) { @@ -752,7 +727,7 @@ async function buildExplicitExternalUtxo( return new ExternalUtxo( outpoint.vout(), tx, - TxOutSecrets.fromExplicit(parseAssetId(assetIdString), amount), + TxOutSecrets.fromExplicit(AssetId.fromString(assetIdString), amount), DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, true, ) @@ -792,7 +767,3 @@ function assertWalletUtxoAssetAndAmount( throw new Error(`${label} UTXO amount is lower than ${minAmount.toString()}`) } } - -function emptyContractHash(): ContractHash { - return ContractHash.fromBytes(new Uint8Array(32)) -} diff --git a/web-v2/src/pages/Dashboard/WalletDemo.tsx b/web-v2/src/pages/Dashboard/WalletDemo.tsx index ccf8e1c..6a17749 100644 --- a/web-v2/src/pages/Dashboard/WalletDemo.tsx +++ b/web-v2/src/pages/Dashboard/WalletDemo.tsx @@ -1,8 +1,6 @@ import type { XOnlyPublicKey } from 'lwk_web' import { useEffect, useState } from 'react' -import { fetchTxConfirmations } from '@/api/esplora/methods' -import { getTxExplorerUrl } from '@/api/esplora/utils' import { env } from '@/constants/env' import type { ConnectionStatus, WalletType } from '@/lib/wallet-core/types' import { useLwk } from '@/providers/lwk/useLwk' @@ -31,7 +29,6 @@ export function WalletDemo() { balances, usbDeviceDetected, connect, - sendLbtc, getReceiveAddress, verifyReceiveAddress, getXOnlyPublicKey, @@ -41,10 +38,6 @@ export function WalletDemo() { const [walletType, setWalletType] = useState('Wpkh') const [sendAddress, setSendAddress] = useState('') const [sendAmount, setSendAmount] = useState('') - const [sendTxid, setSendTxid] = useState(null) - const [sendError, setSendError] = useState(null) - const [sending, setSending] = useState(false) - const [txConfirmations, setTxConfirmations] = useState(null) const [verifyingAddress, setVerifyingAddress] = useState(false) const [xOnlyPubKey, setXOnlyPubKey] = useState(null) const [receiveAddress, setReceiveAddress] = useState(null) @@ -75,24 +68,6 @@ export function WalletDemo() { } }, [connectionStatus, getReceiveAddress]) - // Poll Esplora directly for first confirmation after sending. - useEffect(() => { - if (!sendTxid || txConfirmations !== null) return - - const id = setInterval(() => { - fetchTxConfirmations(sendTxid) - .then(confs => { - if (confs !== null && confs >= 1) { - setTxConfirmations(confs) - clearInterval(id) - } - }) - .catch(console.warn) - }, 15_000) - - return () => clearInterval(id) - }, [sendTxid, txConfirmations]) - const phase = resolvePhase(connectionStatus, usbDeviceDetected, syncing) const handleVerifyAddress = async () => { @@ -108,26 +83,6 @@ export function WalletDemo() { } } - const handleSend = async () => { - setSendError(null) - setSendTxid(null) - setSending(true) - console.warn('[Dashboard] handleSend: start', { sendAddress, sendAmount }) - try { - const txid = await sendLbtc(sendAddress, BigInt(sendAmount)) - console.warn('[Dashboard] handleSend: txid received', txid) - setSendTxid(txid) - setSendAddress('') - setSendAmount('') - setTxConfirmations(null) - } catch (err) { - console.warn('[Dashboard] handleSend: error', err) - setSendError(err instanceof Error ? err.message : String(err)) - } finally { - setSending(false) - } - } - return (
{phase === 'no-usb' && ( @@ -256,34 +211,6 @@ export function WalletDemo() { value={sendAmount} onChange={e => setSendAmount(e.target.value)} /> - - {sendTxid && ( -
-

- Sent!{' '} - - {sendTxid} - -

-

- {txConfirmations !== null - ? `${txConfirmations} confirmation${txConfirmations === 1 ? '' : 's'}` - : 'Waiting for confirmation...'} -

-
- )} - {sendError &&

{sendError}

}
)} diff --git a/web-v2/src/providers/wallet/WalletProvider.tsx b/web-v2/src/providers/wallet/WalletProvider.tsx index e812867..8850004 100644 --- a/web-v2/src/providers/wallet/WalletProvider.tsx +++ b/web-v2/src/providers/wallet/WalletProvider.tsx @@ -262,7 +262,7 @@ export function WalletProvider({ children }: { children: React.ReactNode }) { const session = sessionRef.current if (!session) return null if (!session.connector.getVerifiedReceiveAddress) - return session.wollet.address().address().toString() + return session.wollet.address(0).address().toString() return session.connector.getVerifiedReceiveAddress(state.walletType ?? 'Wpkh', session.wollet) }, [state.walletType]) From e014fae58c5fd8db92e72af0865f42c1eb922bd3 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 5 Jun 2026 18:18:20 +0300 Subject: [PATCH 11/16] feat(demos): add CreateOfferDemo for offer creation transactions chore(demos): remove deprecated ScriptAuthCovenantDemo refactor(demos): enhance helpers for ScriptAuth state management fix(dashboard): update import paths for demo components --- .../Dashboard/CreateBorrowerAccountDemo.tsx | 386 ----------------- .../Dashboard/{ => Demos}/CreateOfferDemo.tsx | 18 +- web-v2/src/pages/Dashboard/Demos/helpers.ts | 33 ++ .../Dashboard/ScriptAuthCovenantDemo.tsx | 401 ------------------ web-v2/src/pages/Dashboard/index.tsx | 4 +- 5 files changed, 47 insertions(+), 795 deletions(-) delete mode 100644 web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx rename web-v2/src/pages/Dashboard/{ => Demos}/CreateOfferDemo.tsx (98%) delete mode 100644 web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx diff --git a/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx b/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx deleted file mode 100644 index cdffeb5..0000000 --- a/web-v2/src/pages/Dashboard/CreateBorrowerAccountDemo.tsx +++ /dev/null @@ -1,386 +0,0 @@ -import { - Address, - assetIdFromIssuance, - ContractHash, - IssuanceRecipient, - Script, - TxBuilder, - type XOnlyPublicKey, -} from 'lwk_web' -import { useState } from 'react' - -import { broadcastTx } from '@/api/esplora/methods' -import { getTxExplorerUrl } from '@/api/esplora/utils' -import { getPolicyAssetUtxos, outpointToString } from '@/lwk/utxo' -import { useLwk } from '@/providers/lwk/useLwk' -import { useWallet } from '@/providers/wallet/useWallet' -import { loadIssuanceFactoryProgram } from '@/simplicity/issuance-factory/program' -import { useTxConfirmations } from '@/simplicity/script-auth/helpers' -import { bytesToHex } from '@/utils/hex' - -interface CreateBorrowerAccountSummary { - fundingOutpoint: string - factoryAddress: string - factoryAuthOutpoint: string - issuanceFactoryOutpoint: string - issuedAssetId: string - issuanceAmount: string - reissuanceTokenAmount: string - metadataOpReturnHex: string - metadataIncluded: boolean -} - -interface RemoveBorrowerAccountSummary { - factoryOutpoint: string -} - -interface BorrowerAccountState { - creationTxid: string - factoryAddress: string - factoryAuthOutpoint: string - issuanceFactoryOutpoint: string - issuedAssetId: string - metadataOpReturnHex: string -} - -interface BroadcastState { - busy: boolean - error: string | null - summary: TSummary | null - txid: string | null -} - -const BORROWER_ACCOUNT_STORAGE_KEY = 'borrower-account-demo' -const DEFAULT_FEE_RESERVE = 10_000n -const ISSUING_UTXOS_COUNT = 2 -const REISSUANCE_FLAGS = 0n -const ISSUANCE_AMOUNT = 2n -const REISSUANCE_TOKEN_AMOUNT = 0n -const FACTORY_AUTH_AMOUNT = 1n -const ISSUANCE_FACTORY_AMOUNT = 1n - -const INITIAL_BROADCAST_STATE = { - busy: false, - error: null, - summary: null, - txid: null, -} - -export default function CreateBorrowerAccountDemo() { - const { lwkNetwork } = useLwk() - const { - connectionStatus, - getReceiveAddress, - getWalletUtxos, - getWollet, - getXOnlyPublicKey, - signPset, - } = useWallet() - - const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) - const [createState, setCreateState] = useState>({ - ...INITIAL_BROADCAST_STATE, - }) - const [removeState, setRemoveState] = useState>({ - ...INITIAL_BROADCAST_STATE, - }) - - const createConfirmations = useTxConfirmations(createState.txid) - const removeConfirmations = useTxConfirmations(removeState.txid) - - const createBorrowerAccount = async () => { - setCreateState(state => ({ - ...state, - busy: true, - error: null, - summary: null, - txid: null, - })) - - try { - const key = await getXOnlyPublicKey() - if (!key) { - throw new Error('Missing x-only public key') - } - setXOnlyPublicKey(key) - - const receiveAddressString = await getReceiveAddress() - if (!receiveAddressString) { - throw new Error('Missing receive address') - } - - const wollet = await getWollet() - if (!wollet) { - throw new Error('Wallet not connected') - } - - const policyAsset = lwkNetwork.policyAsset() - const walletUtxos = await getWalletUtxos() - const feeUtxo = getPolicyAssetUtxos(walletUtxos, policyAsset) - .filter(utxo => utxo.unblinded().value() > DEFAULT_FEE_RESERVE) - .sort((a, b) => Number(a.unblinded().value() - b.unblinded().value()))[0] - - if (!feeUtxo) { - throw new Error('Need a wallet L-BTC UTXO larger than the fee reserve') - } - - const fundingOutpoint = outpointToString(feeUtxo) - const receiveAddress = Address.parse(receiveAddressString, lwkNetwork).toUnconfidential() - const issuanceFactoryProgram = loadIssuanceFactoryProgram({ - issuingUtxosCount: ISSUING_UTXOS_COUNT, - reissuanceFlags: REISSUANCE_FLAGS, - }) - const factoryAddress = issuanceFactoryProgram.createP2trAddress(key, lwkNetwork) - const factoryAddressString = factoryAddress.toString() - const issuedAssetId = assetIdFromIssuance(feeUtxo.outpoint(), emptyContractHash()) - - const metadata = await buildIssuanceFactoryMetadata() - - if (FACTORY_AUTH_AMOUNT + ISSUANCE_FACTORY_AMOUNT !== ISSUANCE_AMOUNT) { - throw new Error('invalid issuance split') - } - - const issuanceRecipients = [ - IssuanceRecipient.fromAddress(FACTORY_AUTH_AMOUNT, receiveAddress), - IssuanceRecipient.fromAddress(ISSUANCE_FACTORY_AMOUNT, factoryAddress), - ] - - const pset = new TxBuilder(lwkNetwork) - .setWalletUtxos([feeUtxo.outpoint()]) - .issueAssetToRecipients(issuanceRecipients, REISSUANCE_TOKEN_AMOUNT, null, null) - .addExplicitScriptOutput(Script.newOpReturn(metadata), 0n, policyAsset) - .finish(wollet) - - const signedPset = await signPset(pset) - const finalizedPset = wollet.finalize(signedPset) - const tx = finalizedPset.extractTx() - const creationTxid = await broadcastTx(tx.toString()) - - const savedState: BorrowerAccountState = { - creationTxid, - factoryAddress: factoryAddressString, - factoryAuthOutpoint: `${creationTxid}:0`, - issuanceFactoryOutpoint: `${creationTxid}:1`, - issuedAssetId: issuedAssetId.toString(), - metadataOpReturnHex: bytesToHex(Script.newOpReturn(metadata).bytes()), - } - saveBorrowerAccountState(savedState) - - setCreateState({ - busy: false, - error: null, - summary: { - fundingOutpoint, - factoryAddress: savedState.factoryAddress, - factoryAuthOutpoint: savedState.factoryAuthOutpoint, - issuanceFactoryOutpoint: savedState.issuanceFactoryOutpoint, - issuedAssetId: savedState.issuedAssetId, - issuanceAmount: ISSUANCE_AMOUNT.toString(), - reissuanceTokenAmount: REISSUANCE_TOKEN_AMOUNT.toString(), - metadataOpReturnHex: savedState.metadataOpReturnHex, - metadataIncluded: true, - }, - txid: creationTxid, - }) - } catch (err) { - setCreateState(state => ({ - ...state, - busy: false, - error: err instanceof Error ? err.message : String(err), - })) - } - } - - const removeBorrowerAccount = async () => { - setRemoveState(state => ({ - ...state, - busy: true, - error: null, - summary: null, - txid: null, - })) - - try { - if (!latestBorrowerAccountState()) { - throw new Error('Create a borrower account first') - } - - throw new Error( - 'Remove is scaffolded but not wired: the wallet connector must expose Schnorr signing for IssuanceFactory sig_all_hash.', - ) - } catch (err) { - setRemoveState(state => ({ - ...state, - busy: false, - error: err instanceof Error ? err.message : String(err), - })) - } - } - - const busy = createState.busy || removeState.busy - const disabled = connectionStatus !== 'ready' || busy - - return ( -
-
-
Borrower Account IssuanceFactory Demo
-

- Creates a borrower account by issuing two units of a new auth asset from one wallet L-BTC - input. One unit returns to the user as FactoryAuth, and one unit funds the IssuanceFactory - covenant. Reissuance token amount is zero. -

- -
- - - -
- - {createState.error && ( -

Create: {createState.error}

- )} - {removeState.error && ( -

Remove: {removeState.error}

- )} - -
- - - -
- -
-          {JSON.stringify(
-            {
-              connectionStatus,
-              hasPubkey: !!xOnlyPublicKey,
-              latestSavedState: latestBorrowerAccountState(),
-              constants: {
-                issuingUtxosCount: ISSUING_UTXOS_COUNT,
-                reissuanceFlags: REISSUANCE_FLAGS.toString(),
-                issuanceAmount: ISSUANCE_AMOUNT.toString(),
-                reissuanceTokenAmount: REISSUANCE_TOKEN_AMOUNT.toString(),
-              },
-              create: {
-                broadcasting: createState.busy,
-                txid: createState.txid,
-                confirmations: createConfirmations,
-                error: createState.error,
-              },
-              remove: {
-                broadcasting: removeState.busy,
-                txid: removeState.txid,
-                confirmations: removeConfirmations,
-                error: removeState.error,
-              },
-            },
-            null,
-            2,
-          )}
-        
-
-
- ) -} - -async function buildIssuanceFactoryMetadata(): Promise { - const programId = await getIssuanceFactoryProgramId() - const data = new Uint8Array(13) - data.set(programId, 0) - data[4] = ISSUING_UTXOS_COUNT - new DataView(data.buffer).setBigUint64(5, REISSUANCE_FLAGS, true) - return data -} - -function emptyContractHash(): ContractHash { - return ContractHash.fromBytes(new Uint8Array(32)) -} - -async function getIssuanceFactoryProgramId(): Promise { - const { sources } = await import('virtual:simplicity-sources') - const hash = await crypto.subtle.digest( - 'SHA-256', - new TextEncoder().encode(sources.issuance_factory), - ) - return new Uint8Array(hash).slice(0, 4) -} - -function getBorrowerAccountStates(): BorrowerAccountState[] { - const raw = localStorage.getItem(BORROWER_ACCOUNT_STORAGE_KEY) - if (!raw) { - return [] - } - - return JSON.parse(raw) -} - -function latestBorrowerAccountState(): BorrowerAccountState | null { - const states = getBorrowerAccountStates() - return states[states.length - 1] ?? null -} - -function saveBorrowerAccountState(state: BorrowerAccountState): void { - const existingStates = getBorrowerAccountStates() - existingStates.push(state) - localStorage.setItem(BORROWER_ACCOUNT_STORAGE_KEY, JSON.stringify(existingStates)) -} - -function BroadcastResult({ - title, - txid, - confirmations, - summary, -}: { - title: string - txid: string | null - confirmations: number | null - summary?: unknown -}) { - if (!txid) { - return null - } - - return ( -
-
{title}
- - {txid} - -
- Confirmations: {confirmations === null ? 'waiting…' : confirmations} -
- {summary ? ( -
-          {JSON.stringify(summary, null, 2)}
-        
- ) : null} -
- ) -} diff --git a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx b/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx similarity index 98% rename from web-v2/src/pages/Dashboard/CreateOfferDemo.tsx rename to web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx index c39f820..d042fab 100644 --- a/web-v2/src/pages/Dashboard/CreateOfferDemo.tsx +++ b/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx @@ -22,7 +22,7 @@ import { getTxExplorerUrl } from '@/api/esplora/utils' import { UiButton } from '@/components/ui/UiButton' import { UiSelect, type UiSelectOption } from '@/components/ui/UiSelect' import { UiTextField } from '@/components/ui/UiTextField' -import { findUtxoByOutpoint, getPolicyAssetUtxos, outpointToString } from '@/lwk/utxo' +import { isPolicyAssetUtxo, utxoToOutpointString } from '@/lwk/utxo' import { useLwk } from '@/providers/lwk/useLwk' import { useWallet } from '@/providers/wallet/useWallet' import { @@ -34,10 +34,11 @@ import { buildPendingOfferMetadata, loadLendingProgram, } from '@/simplicity/lending/program' -import { useTxConfirmations } from '@/simplicity/script-auth/helpers' import { loadScriptAuthProgram } from '@/simplicity/script-auth/program' import { bytesToHex, hexToBytes, isHexStringOfByteLength, normalizeHex } from '@/utils/hex' +import { useTxConfirmations } from './helpers' + const integerStringSchema = (label: string) => zod.string().trim().regex(/^\d+$/, `${label} must be an integer`) @@ -185,7 +186,10 @@ export default function CreateOfferDemo() { const policyAssetId = useMemo(() => lwkNetwork.policyAsset().toString(), [lwkNetwork]) const collateralUtxoOptions = useMemo( - () => getPolicyAssetUtxos(walletUtxos, policyAssetId).map(formatCollateralUtxoOption), + () => + walletUtxos + .filter(utxo => isPolicyAssetUtxo(utxo, policyAssetId)) + .map(formatCollateralUtxoOption), [policyAssetId, walletUtxos], ) @@ -247,7 +251,9 @@ export default function CreateOfferDemo() { await syncWallet() const walletUtxos = await getWalletUtxos() setWalletUtxos(walletUtxos) - const factoryAuthUtxo = findUtxoByOutpoint(walletUtxos, parsedForm.factoryAuthOutpoint) + const factoryAuthUtxo = walletUtxos.find( + utxo => utxoToOutpointString(utxo) === parsedForm.factoryAuthOutpoint, + ) const collateralUtxo = requireWalletUtxo( walletUtxos, parsedForm.collateralOutpoint, @@ -702,7 +708,7 @@ function BroadcastResult({ } function formatCollateralUtxoOption(utxo: WalletTxOut): UiSelectOption { - const outpoint = outpointToString(utxo) + const outpoint = utxoToOutpointString(utxo) const height = utxo.height() const status = height === undefined ? 'mempool' : `height ${height}` return { @@ -746,7 +752,7 @@ function requireWalletUtxo( outpoint: string, label: string, ): WalletTxOut { - const utxo = findUtxoByOutpoint(walletUtxos, outpoint.trim()) + const utxo = walletUtxos.find(utxo => utxoToOutpointString(utxo) === outpoint.trim()) if (!utxo) { throw new Error(`${label} wallet UTXO not found`) } diff --git a/web-v2/src/pages/Dashboard/Demos/helpers.ts b/web-v2/src/pages/Dashboard/Demos/helpers.ts index 39388e8..83ee678 100644 --- a/web-v2/src/pages/Dashboard/Demos/helpers.ts +++ b/web-v2/src/pages/Dashboard/Demos/helpers.ts @@ -53,6 +53,11 @@ export interface SavedScriptAuthState { fundingTxid: string } +export function latestScriptAuthState() { + const states = getScriptAuthStates() + return states[states.length - 1] ?? null +} + export function useTxConfirmations(txid: string | null): number | null { const [confirmedTx, setConfirmedTx] = useState<{ confirmations: number @@ -101,3 +106,31 @@ export function useTxConfirmations(txid: string | null): number | null { return confirmedTx?.txid === txid ? confirmedTx.confirmations : null } + +const SCRIPT_AUTH_STORAGE_KEY = 'script-auth-covenants' + +export function getScriptAuthStates(): SavedScriptAuthState[] { + const raw = localStorage.getItem(SCRIPT_AUTH_STORAGE_KEY) + + if (!raw) { + return [] + } + + return JSON.parse(raw) +} + +export function saveScriptAuthState(state: SavedScriptAuthState): void { + const existingStates = getScriptAuthStates() + + existingStates.push(state) + + localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(existingStates)) +} + +export function removeScriptAuthState(authOutpoint: string): void { + const existingStates = getScriptAuthStates() + + const filteredStates = existingStates.filter(state => state.authOutpoint !== authOutpoint) + + localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(filteredStates)) +} diff --git a/web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx b/web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx deleted file mode 100644 index 619fc13..0000000 --- a/web-v2/src/pages/Dashboard/ScriptAuthCovenantDemo.tsx +++ /dev/null @@ -1,401 +0,0 @@ -// This file is a temporary playground for testing ScriptAuth covenants in a real wallet environment. -// It is not intended to be a long-term part of the codebase, and may be deleted or significantly refactored in the future. -// The demo performs the following steps: -// 1. User clicks "Fund Covenant UTXO" button and waits at least 1 confirmation -// 2. User clicks "Spend Covenant UTXO" button, which attempts to spend the covenant UTXO using the auth UTXO and logs the result -import { - Address, - ExternalUtxo, - SimplicityLogLevel, - Transaction, - TxBuilder, - TxOutSecrets, - type XOnlyPublicKey, -} from 'lwk_web' -import { useState } from 'react' - -import { broadcastTx, fetchAddressUtxo, fetchTxRaw } from '@/api/esplora/methods' -import { getTxExplorerUrl } from '@/api/esplora/utils' -import { utxoToOutpointString } from '@/lwk/utxo' -import { selectDemoScriptAuthInputs, useTxConfirmations } from '@/pages/Dashboard/Demos/helpers' -import { useLwk } from '@/providers/lwk/useLwk' -import { useWallet } from '@/providers/wallet/useWallet' -import { latestScriptAuthState, saveScriptAuthState } from '@/simplicity/script-auth/helpers' -import { buildScriptAuthWitness, loadScriptAuthProgram } from '@/simplicity/script-auth/program' -import { hexToBytes } from '@/utils/hex' - -interface FundingSummary { - covenantAddress: string - fundingOutpoint: string - authOutpoint: string - scriptHashHex: string - amount: string -} - -interface SpendSummary { - covenantOutpoint: string - authOutpoint: string - recipientAddress: string - amount: string -} - -interface BroadcastState { - busy: boolean - error: string | null - summary: TSummary | null - txid: string | null -} - -const DEFAULT_SCRIPT_AUTH_FEE_RATE = 100 -const DEFAULT_SCRIPT_AUTH_FEE_RESERVE = 10_000n -const DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY = 20_000 - -const INITIAL_BROADCAST_STATE = { - busy: false, - error: null, - summary: null, - txid: null, -} - -export default function ScriptAuthCovenantDemo() { - const { lwkNetwork } = useLwk() - const { - connectionStatus, - getXOnlyPublicKey, - getReceiveAddress, - getWalletUtxos, - getWollet, - signPset, - } = useWallet() - - const [xOnlyPublicKey, setXOnlyPublicKey] = useState(null) - - const [fundingState, setFundingState] = useState>({ - ...INITIAL_BROADCAST_STATE, - }) - const [spendState, setSpendState] = useState>({ - ...INITIAL_BROADCAST_STATE, - }) - - const fundingConfirmations = useTxConfirmations(fundingState.txid) - const spendConfirmations = useTxConfirmations(spendState.txid) - - const fundCovenant = async () => { - setFundingState(state => ({ - ...state, - busy: true, - error: null, - summary: null, - txid: null, - })) - - try { - const key = await getXOnlyPublicKey() - if (!key) { - throw new Error('Missing x-only public key') - } - setXOnlyPublicKey(key) - - const walletUtxos = await getWalletUtxos() - const policyAsset = lwkNetwork.policyAsset() - const { authUtxo, fundingUtxo } = selectDemoScriptAuthInputs( - walletUtxos, - policyAsset, - DEFAULT_SCRIPT_AUTH_FEE_RESERVE, - ) - const scriptHashHex = authUtxo.scriptPubkey().jet_sha256_hex() - const scriptAuthProgram = loadScriptAuthProgram(hexToBytes(scriptHashHex)) - const scriptAuthAddress = scriptAuthProgram.createP2trAddress(key, lwkNetwork) - const covenantAddress = scriptAuthAddress.toString() - - const fundingAmount = fundingUtxo.unblinded().value() - DEFAULT_SCRIPT_AUTH_FEE_RESERVE - const fundingOutpoint = utxoToOutpointString(fundingUtxo) - - const authOutpoint = utxoToOutpointString(authUtxo) - - const wollet = await getWollet() - - const fundingPset = new TxBuilder(lwkNetwork) - .feeRate(DEFAULT_SCRIPT_AUTH_FEE_RATE) - .setWalletUtxos([fundingUtxo.outpoint()]) - .addExplicitRecipient(scriptAuthAddress, fundingAmount, policyAsset) - .finish(wollet) - - const signedPset = await signPset(fundingPset) - const finalizedPset = wollet.finalize(signedPset) - const fundingTx = finalizedPset.extractTx() - const fundingTxid = await broadcastTx(fundingTx.toString()) - - saveScriptAuthState({ - authOutpoint, - scriptHashHex, - fundingTxid, - }) - - setFundingState({ - busy: false, - error: null, - summary: { - covenantAddress, - fundingOutpoint, - authOutpoint, - scriptHashHex, - amount: fundingAmount.toString(), - }, - txid: fundingTxid, - }) - } catch (err) { - setFundingState(state => ({ - ...state, - busy: false, - error: err instanceof Error ? err.message : String(err), - })) - } - } - - const spendCovenantUtxo = async () => { - setSpendState(state => ({ - ...state, - busy: true, - error: null, - summary: null, - txid: null, - })) - - try { - const scriptAuthState = latestScriptAuthState() - if (!scriptAuthState) { - throw new Error('Failed to prepare ScriptAuth state') - } - const { scriptHashHex } = scriptAuthState - - const scriptAuthProgram = loadScriptAuthProgram(hexToBytes(scriptHashHex)) - const key = xOnlyPublicKey ?? (await getXOnlyPublicKey()) - if (!key) { - throw new Error('Missing x-only public key') - } - setXOnlyPublicKey(key) - - const scriptAuthAddress = scriptAuthProgram.createP2trAddress(key, lwkNetwork) - const scriptAuthAddressString = scriptAuthAddress.toString() - - const covenantUtxos = await fetchAddressUtxo(scriptAuthAddressString) - const covenantUtxo = - covenantUtxos.find(utxo => utxo.txid === scriptAuthState.fundingTxid) ?? covenantUtxos[0] - if (!covenantUtxo) { - throw new Error('ScriptAuth covenant UTXO not found') - } - - const walletUtxos = await getWalletUtxos() - - const authUtxo = - walletUtxos.find(utxo => utxoToOutpointString(utxo) === scriptAuthState.authOutpoint) ?? - null - if (!authUtxo) { - throw new Error('ScriptAuth auth UTXO not found') - } - const authOutpointString = utxoToOutpointString(authUtxo) - const covenantOutpoint = `${covenantUtxo.txid}:${covenantUtxo.vout}` - - const covenantTx = Transaction.fromBytes(await fetchTxRaw(covenantUtxo.txid)) - const covenantTxOut = covenantTx.outputs[covenantUtxo.vout] - if (!covenantTxOut) { - throw new Error('ScriptAuth covenant funding transaction does not have the UTXO output') - } - - const authOutpoint = authUtxo.outpoint() - const authTx = Transaction.fromBytes(await fetchTxRaw(authOutpoint.txid().toString())) - const authTxOut = authTx.outputs[authOutpoint.vout()] - - if (!authTxOut) { - throw new Error('ScriptAuth auth transaction does not have the selected output') - } - - const policyAsset = lwkNetwork.policyAsset() - const covenantValue = covenantUtxo.value - - if (!covenantValue) { - throw new Error('ScriptAuth covenant UTXO value is missing') - } - - const covenantExternalUtxo = new ExternalUtxo( - covenantUtxo.vout, - covenantTx, - TxOutSecrets.fromExplicit(policyAsset, BigInt(covenantValue)), - DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, - true, - ) - - const receiveAddress = await getReceiveAddress() - if (!receiveAddress) { - throw new Error('Missing receive address') - } - - const wollet = await getWollet() - - const recipientAddress = Address.parse(receiveAddress, lwkNetwork).toUnconfidential() - const recipientAddressString = recipientAddress.toString() - const spendPset = new TxBuilder(lwkNetwork) - .feeRate(DEFAULT_SCRIPT_AUTH_FEE_RATE) - .setWalletUtxos([authOutpoint]) - .addExternalUtxos([covenantExternalUtxo]) - .addExplicitRecipient(recipientAddress, BigInt(covenantValue), policyAsset) - .finish(wollet) - - const signedSpendPset = await signPset(spendPset) - const finalizedWalletPset = wollet.finalize(signedSpendPset) - const txWithWalletWitness = finalizedWalletPset.extractTx() - - const COVENANT_INPUT_INDEX = 0 - const AUTH_INPUT_INDEX = 1 - - const finalizedTx = scriptAuthProgram.finalizeTransaction( - txWithWalletWitness, - key, - [covenantTxOut, authTxOut], - COVENANT_INPUT_INDEX, - buildScriptAuthWitness(AUTH_INPUT_INDEX), - lwkNetwork, - SimplicityLogLevel.Trace, - ) - - const spendTxid = await broadcastTx(finalizedTx.toString()) - - setSpendState({ - busy: false, - error: null, - summary: { - covenantOutpoint, - authOutpoint: authOutpointString, - recipientAddress: recipientAddressString, - amount: covenantValue.toString(), - }, - txid: spendTxid, - }) - } catch (err) { - setSpendState(state => ({ - ...state, - busy: false, - error: err instanceof Error ? err.message : String(err), - })) - } - } - - const busy = fundingState.busy || spendState.busy - const disabled = connectionStatus !== 'ready' || busy - - return ( -
-
-
ScriptAuth Covenant Smoke Test
- -
- - - -
- - {fundingState.error && ( -

Funding: {fundingState.error}

- )} - {spendState.error &&

Spend: {spendState.error}

} - -
- - - -
- -
-          {JSON.stringify(
-            {
-              connectionStatus,
-              hasPubkey: !!xOnlyPublicKey,
-              latestSavedState: latestScriptAuthState(),
-              funding: {
-                broadcasting: fundingState.busy,
-                txid: fundingState.txid,
-                confirmations: fundingConfirmations,
-                error: fundingState.error,
-              },
-              spend: {
-                broadcasting: spendState.busy,
-                txid: spendState.txid,
-                confirmations: spendConfirmations,
-                error: spendState.error,
-              },
-            },
-            null,
-            2,
-          )}
-        
-
-
- ) -} -function BroadcastResult({ - title, - txid, - confirmations, - summary, -}: { - title: string - txid: string | null - confirmations: number | null - summary?: unknown -}) { - if (!txid) { - return null - } - - return ( -
-
{title}
- -
TXID: {txid}
- - - Open in Explorer - - -

- {confirmations !== null - ? `${confirmations} confirmation${confirmations === 1 ? '' : 's'}` - : 'Waiting for confirmation...'} -

- - {summary !== undefined && ( -
-          {JSON.stringify(summary, null, 2)}
-        
- )} -
- ) -} diff --git a/web-v2/src/pages/Dashboard/index.tsx b/web-v2/src/pages/Dashboard/index.tsx index ffcd8b8..db1e3ac 100644 --- a/web-v2/src/pages/Dashboard/index.tsx +++ b/web-v2/src/pages/Dashboard/index.tsx @@ -1,5 +1,5 @@ -import CreateBorrowerAccountDemo from './CreateBorrowerAccountDemo' -import CreateOfferDemo from './CreateOfferDemo' +import CreateBorrowerAccountDemo from './Demos/CreateBorrowerAccountDemo' +import CreateOfferDemo from './Demos/CreateOfferDemo' import ScriptAuthCovenantDemo from './Demos/ScriptAuthCovenantDemo' import { WalletDemo } from './Demos/WalletDemo' From 1034aa71896d60394100d575d67e7794f7627036 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 5 Jun 2026 18:32:12 +0300 Subject: [PATCH 12/16] feat(demos): integrate create offer functionality in demo --- web-v2/src/hooks/useCreateOffer.ts | 414 +++++++++++++++ .../pages/Dashboard/Demos/CreateOfferDemo.tsx | 502 +----------------- web-v2/src/pages/Dashboard/Demos/helpers.ts | 10 + 3 files changed, 447 insertions(+), 479 deletions(-) create mode 100644 web-v2/src/hooks/useCreateOffer.ts diff --git a/web-v2/src/hooks/useCreateOffer.ts b/web-v2/src/hooks/useCreateOffer.ts new file mode 100644 index 0000000..f7bc887 --- /dev/null +++ b/web-v2/src/hooks/useCreateOffer.ts @@ -0,0 +1,414 @@ +import { + Address, + AssetId, + assetIdFromIssuance, + ContractHash, + ExternalUtxo, + IssuanceRecipient, + OutPoint, + Script, + SimplicityLogLevel, + Transaction, + TxBuilder, + TxOutSecrets, + type WalletTxOut, +} from 'lwk_web' + +import { broadcastTx, fetchLatestBlockHeight, fetchTxRaw } from '@/api/esplora/methods' +import { utxoToOutpointString } from '@/lwk/utxo' +import { useLwk } from '@/providers/lwk/useLwk' +import { useWallet } from '@/providers/wallet/useWallet' +import { + buildIssuanceFactoryWitness, + loadIssuanceFactoryProgram, +} from '@/simplicity/issuance-factory/program' +import { + buildDerivedLendingOfferProgramParams, + buildPendingOfferMetadata, + loadLendingProgram, +} from '@/simplicity/lending/program' +import { loadScriptAuthProgram } from '@/simplicity/script-auth/program' +import { bytesToHex, hexToBytes } from '@/utils/hex' + +const ISSUING_UTXOS_COUNT = 2 +const REISSUANCE_FLAGS = 0n +const REISSUANCE_TOKEN_AMOUNT = 0n +const NFT_AMOUNT = 1n +const DEFAULT_FEE_RATE = 100 +const DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY = 30_000 + +export interface CreateOfferParams { + factoryAuthOutpoint: string + issuanceFactoryOutpoint: string + factoryAssetId: string + collateralOutpoint: string + collateralAmount: bigint + principalAssetId: string + principalAmount: bigint + principalInterestRate: number + loanDurationBlocks: number + protocolFeeKeeperAssetId: string +} + +export interface CreateOfferSummary { + inputs: Record + outputs: Record + assetIds: Record + scripts: Record + offerParameters: Record + metadataOpReturnHex: string +} + +export interface CreateOfferResult { + txid: string + summary: CreateOfferSummary +} + +export function useCreateOffer() { + const { lwkNetwork } = useLwk() + const { getReceiveAddress, getWalletUtxos, getWollet, getXOnlyPublicKey, signPset, syncWallet } = + useWallet() + + const createOffer = async (params: CreateOfferParams): Promise => { + let stage = 'initializing' + try { + stage = 'get x-only public key' + const xOnlyPublicKey = await getXOnlyPublicKey() + if (!xOnlyPublicKey) throw new Error('Missing x-only public key') + + stage = 'get receive address' + const receiveAddressString = await getReceiveAddress() + if (!receiveAddressString) throw new Error('Missing receive address') + + stage = 'get wollet' + const wollet = await getWollet() + if (!wollet) throw new Error('Wallet not connected') + + stage = 'sync wallet and load UTXOs' + await syncWallet() + const walletUtxos = await getWalletUtxos() + const factoryAuthUtxo = walletUtxos.find( + utxo => utxoToOutpointString(utxo) === params.factoryAuthOutpoint, + ) + const collateralUtxo = requireWalletUtxo(walletUtxos, params.collateralOutpoint, 'Collateral') + + stage = 'prepare validated params' + const factoryAsset = AssetId.fromString(params.factoryAssetId) + const principalAsset = AssetId.fromString(params.principalAssetId) + const protocolFeeKeeperAsset = AssetId.fromString(params.protocolFeeKeeperAssetId) + const policyAsset = lwkNetwork.policyAsset() + const factoryAssetString = factoryAsset.toString() + const principalAssetString = principalAsset.toString() + const protocolFeeKeeperAssetString = protocolFeeKeeperAsset.toString() + const policyAssetString = policyAsset.toString() + + if (factoryAuthUtxo) { + assertWalletUtxoAssetAndAmount( + factoryAuthUtxo, + factoryAssetString, + NFT_AMOUNT, + 'FactoryAuth', + ) + } + assertWalletUtxoAssetAndAmount( + collateralUtxo, + policyAssetString, + params.collateralAmount, + 'Collateral', + ) + + const issuanceFactoryOutpoint = new OutPoint(params.issuanceFactoryOutpoint) + const collateralOutpoint = new OutPoint(params.collateralOutpoint) + + stage = 'prepare addresses and IssuanceFactory external UTXO' + const receiveAddressExplicitString = Address.parse(receiveAddressString, lwkNetwork) + .toUnconfidential() + .toString() + const issuanceFactoryProgram = loadIssuanceFactoryProgram({ + issuingUtxosCount: ISSUING_UTXOS_COUNT, + reissuanceFlags: REISSUANCE_FLAGS, + }) + const issuanceFactoryAddress = issuanceFactoryProgram.createP2trAddress( + xOnlyPublicKey, + lwkNetwork, + ) + const issuanceFactoryAddressString = issuanceFactoryAddress.toString() + + const issuanceFactoryTx = Transaction.fromBytes( + await fetchTxRaw(issuanceFactoryOutpoint.txid().toString()), + ) + const issuanceFactoryTxOut = issuanceFactoryTx.outputs[issuanceFactoryOutpoint.vout()] + if (!issuanceFactoryTxOut) + throw new Error('IssuanceFactory transaction does not have the selected output') + + const issuanceFactoryExternalUtxo = new ExternalUtxo( + issuanceFactoryOutpoint.vout(), + issuanceFactoryTx, + TxOutSecrets.fromExplicit(AssetId.fromString(factoryAssetString), NFT_AMOUNT), + DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, + true, + ) + const factoryAuthExternalUtxo = factoryAuthUtxo + ? null + : await buildExplicitExternalUtxo( + params.factoryAuthOutpoint, + factoryAssetString, + NFT_AMOUNT, + 'FactoryAuth', + ) + + const borrowerNftAsset = assetIdFromIssuance( + issuanceFactoryOutpoint, + ContractHash.fromBytes(new Uint8Array(32)), + ) + const lenderNftAsset = assetIdFromIssuance( + collateralOutpoint, + ContractHash.fromBytes(new Uint8Array(32)), + ) + const borrowerNftAssetString = borrowerNftAsset.toString() + const lenderNftAssetString = lenderNftAsset.toString() + const currentBlockHeight = await fetchLatestBlockHeight() + const loanDurationBlocks = params.loanDurationBlocks + const offerParameters = { + collateralAmount: params.collateralAmount, + principalAmount: params.principalAmount, + principalInterestRate: params.principalInterestRate, + loanExpirationTime: currentBlockHeight + loanDurationBlocks, + } + + stage = 'compile lending and ScriptAuth programs' + const derivedLendingParams = buildDerivedLendingOfferProgramParams( + { + collateralAssetId: AssetId.fromString(policyAssetString).toBytes(), + principalAssetId: AssetId.fromString(principalAssetString).toBytes(), + borrowerNftAssetId: AssetId.fromString(borrowerNftAssetString).toBytes(), + lenderNftAssetId: AssetId.fromString(lenderNftAssetString).toBytes(), + protocolFeeKeeperAssetId: AssetId.fromString(protocolFeeKeeperAssetString).toBytes(), + offerParameters, + }, + xOnlyPublicKey, + lwkNetwork, + ) + const lendingProgram = loadLendingProgram(derivedLendingParams) + const lendingAddress = lendingProgram.createP2trAddress(xOnlyPublicKey, lwkNetwork) + const lendingAddressString = lendingAddress.toString() + const lendingScriptPubkeyHex = bytesToHex(lendingAddress.scriptPubkey().bytes()) + const lendingScriptHash = hexToBytes(new Script(lendingScriptPubkeyHex).jet_sha256_hex()) + const lenderNftScriptAuthProgram = loadScriptAuthProgram(lendingScriptHash) + const lenderNftScriptAuthAddress = lenderNftScriptAuthProgram.createP2trAddress( + xOnlyPublicKey, + lwkNetwork, + ) + const lenderNftScriptAuthAddressString = lenderNftScriptAuthAddress.toString() + const metadata = await buildPendingOfferMetadata({ + principalAssetId: AssetId.fromString(principalAssetString).toBytes(), + offerParameters, + }) + + stage = 'TxBuilder.new' + let txBuilder = new TxBuilder(lwkNetwork) + + stage = 'TxBuilder.feeRate' + txBuilder = txBuilder.feeRate(DEFAULT_FEE_RATE) + + stage = 'TxBuilder.setInputOrder' + txBuilder = txBuilder.setInputOrder([ + new OutPoint(params.factoryAuthOutpoint), + new OutPoint(params.issuanceFactoryOutpoint), + new OutPoint(params.collateralOutpoint), + ]) + + const externalUtxos = factoryAuthExternalUtxo + ? [factoryAuthExternalUtxo, issuanceFactoryExternalUtxo] + : [issuanceFactoryExternalUtxo] + + stage = 'TxBuilder.addExternalUtxos covenant/explicit inputs' + txBuilder = txBuilder.addExternalUtxos(externalUtxos) + + stage = 'TxBuilder.addExplicitRecipient FactoryAuth back to user' + txBuilder = txBuilder.addExplicitRecipient( + new Address(receiveAddressExplicitString), + NFT_AMOUNT, + AssetId.fromString(factoryAssetString), + ) + + stage = 'TxBuilder.addExplicitRecipient IssuanceFactory covenant' + txBuilder = txBuilder.addExplicitRecipient( + new Address(issuanceFactoryAddressString), + NFT_AMOUNT, + AssetId.fromString(factoryAssetString), + ) + + stage = 'TxBuilder.issueAssetToRecipients Borrower NFT to user' + txBuilder = txBuilder.issueAssetToRecipients( + [IssuanceRecipient.fromAddress(NFT_AMOUNT, new Address(receiveAddressExplicitString))], + REISSUANCE_TOKEN_AMOUNT, + null, + null, + new OutPoint(params.issuanceFactoryOutpoint), + ) + + stage = 'TxBuilder.issueAssetToRecipients Lender NFT to ScriptAuth' + txBuilder = txBuilder.issueAssetToRecipients( + [IssuanceRecipient.fromAddress(NFT_AMOUNT, new Address(lenderNftScriptAuthAddressString))], + REISSUANCE_TOKEN_AMOUNT, + null, + null, + new OutPoint(params.collateralOutpoint), + ) + + stage = 'TxBuilder.addExplicitScriptOutput metadata OP_RETURN' + txBuilder = txBuilder.addExplicitScriptOutput( + Script.newOpReturn(metadata), + 0n, + AssetId.fromString(policyAssetString), + ) + + stage = 'TxBuilder.addExplicitScriptOutput Lending covenant collateral' + txBuilder = txBuilder.addExplicitScriptOutput( + new Script(lendingScriptPubkeyHex), + offerParameters.collateralAmount, + AssetId.fromString(policyAssetString), + ) + + stage = 'TxBuilder.finish' + const pset = txBuilder.finish(wollet) + + stage = 'sign offer PSET' + const signedPset = await signPset(pset) + + stage = 'finalize wallet inputs' + const finalizedWalletPset = wollet.finalize(signedPset) + + stage = 'load previous txouts for Simplicity finalize' + const finalizationFactoryAuthOutpoint = new OutPoint(params.factoryAuthOutpoint) + const finalizationCollateralOutpoint = new OutPoint(params.collateralOutpoint) + const factoryAuthTx = Transaction.fromBytes( + await fetchTxRaw(finalizationFactoryAuthOutpoint.txid().toString()), + ) + const factoryAuthTxOut = factoryAuthTx.outputs[finalizationFactoryAuthOutpoint.vout()] + if (!factoryAuthTxOut) + throw new Error('FactoryAuth transaction does not have the selected output') + + const collateralTx = Transaction.fromBytes( + await fetchTxRaw(finalizationCollateralOutpoint.txid().toString()), + ) + const collateralTxOut = collateralTx.outputs[finalizationCollateralOutpoint.vout()] + if (!collateralTxOut) + throw new Error('Collateral transaction does not have the selected output') + + stage = 'finalize IssuanceFactory covenant input' + const txWithWalletWitnesses = finalizedWalletPset.extractTx() + const finalizedTx = issuanceFactoryProgram.finalizeTransaction( + txWithWalletWitnesses, + xOnlyPublicKey, + [factoryAuthTxOut, issuanceFactoryTxOut, collateralTxOut], + 1, + buildIssuanceFactoryWitness({ branch: 'IssueAssets', outputIndex: 0 }), + lwkNetwork, + SimplicityLogLevel.Trace, + ) + + stage = 'broadcast transaction' + const txid = await broadcastTx(finalizedTx.toString()) + + return { + txid, + summary: { + inputs: { + '0 FactoryAuth': params.factoryAuthOutpoint, + '1 IssuanceFactory covenant': params.issuanceFactoryOutpoint, + '2 Collateral LBTC': params.collateralOutpoint, + }, + outputs: { + '0 FactoryAuth back to user': receiveAddressExplicitString, + '1 IssuanceFactory back to covenant': issuanceFactoryAddressString, + '2 Borrower NFT to user': receiveAddressExplicitString, + '3 Lender NFT to ScriptAuth': lenderNftScriptAuthAddressString, + '4 Metadata OP_RETURN': bytesToHex(Script.newOpReturn(metadata).bytes()), + '5 Lending covenant': lendingAddressString, + }, + assetIds: { + factoryAssetId: factoryAssetString, + collateralAssetId: policyAssetString, + principalAssetId: principalAssetString, + borrowerNftAssetId: borrowerNftAssetString, + lenderNftAssetId: lenderNftAssetString, + protocolFeeKeeperAssetId: protocolFeeKeeperAssetString, + }, + scripts: { + lendingScriptHash: bytesToHex(lendingScriptHash), + lenderVaultCovHash: bytesToHex(derivedLendingParams.lenderVaultCovHash), + finalizedLenderVaultCovHash: bytesToHex( + derivedLendingParams.finalizedLenderVaultCovHash, + ), + protocolFeeVaultCovHash: bytesToHex(derivedLendingParams.protocolFeeVaultCovHash), + finalizedProtocolFeeVaultCovHash: bytesToHex( + derivedLendingParams.finalizedProtocolFeeVaultCovHash, + ), + principalOutputScriptHash: bytesToHex(derivedLendingParams.principalOutputScriptHash), + }, + offerParameters: { + collateralAmount: offerParameters.collateralAmount.toString(), + principalAmount: offerParameters.principalAmount.toString(), + principalInterestRate: offerParameters.principalInterestRate.toString(), + currentBlockHeight: currentBlockHeight.toString(), + loanDurationBlocks: loanDurationBlocks.toString(), + loanExpirationTime: offerParameters.loanExpirationTime.toString(), + }, + metadataOpReturnHex: bytesToHex(Script.newOpReturn(metadata).bytes()), + }, + } + } catch (err) { + const errorMessage = err instanceof Error ? err.message : String(err) + const errorBody = + err instanceof Error && 'body' in err && typeof err.body === 'string' ? err.body : null + throw new Error(`${stage}: ${errorMessage}${errorBody ? ` | response: ${errorBody}` : ''}`) + } + } + + return { createOffer } +} + +async function buildExplicitExternalUtxo( + outpointString: string, + assetIdString: string, + amount: bigint, + label: string, +): Promise { + const outpoint = new OutPoint(outpointString) + const tx = Transaction.fromBytes(await fetchTxRaw(outpoint.txid().toString())) + const txOut = tx.outputs[outpoint.vout()] + if (!txOut) throw new Error(`${label} transaction does not have the selected output`) + + return new ExternalUtxo( + outpoint.vout(), + tx, + TxOutSecrets.fromExplicit(AssetId.fromString(assetIdString), amount), + DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, + true, + ) +} + +function requireWalletUtxo( + walletUtxos: WalletTxOut[], + outpoint: string, + label: string, +): WalletTxOut { + const utxo = walletUtxos.find(u => utxoToOutpointString(u) === outpoint.trim()) + if (!utxo) throw new Error(`${label} wallet UTXO not found`) + return utxo +} + +function assertWalletUtxoAssetAndAmount( + utxo: WalletTxOut, + assetId: string, + minAmount: bigint, + label: string, +) { + const unblinded = utxo.unblinded() + if (unblinded.asset().toString() !== assetId) + throw new Error(`${label} UTXO has unexpected asset ${unblinded.asset().toString()}`) + if (unblinded.value() < minAmount) + throw new Error(`${label} UTXO amount is lower than ${minAmount.toString()}`) +} diff --git a/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx b/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx index d042fab..6670955 100644 --- a/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx +++ b/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx @@ -1,43 +1,19 @@ -import { - Address, - AssetId, - assetIdFromIssuance, - ContractHash, - ExternalUtxo, - IssuanceRecipient, - OutPoint, - Script, - SimplicityLogLevel, - Transaction, - TxBuilder, - TxOutSecrets, - type WalletTxOut, -} from 'lwk_web' +import { type WalletTxOut } from 'lwk_web' import { type ComponentProps, useCallback, useEffect, useMemo, useState } from 'react' import { Controller, type Resolver, useForm } from 'react-hook-form' import { z as zod } from 'zod' -import { broadcastTx, fetchLatestBlockHeight, fetchTxRaw } from '@/api/esplora/methods' -import { getTxExplorerUrl } from '@/api/esplora/utils' import { UiButton } from '@/components/ui/UiButton' -import { UiSelect, type UiSelectOption } from '@/components/ui/UiSelect' +import { UiSelect } from '@/components/ui/UiSelect' import { UiTextField } from '@/components/ui/UiTextField' -import { isPolicyAssetUtxo, utxoToOutpointString } from '@/lwk/utxo' +import { type CreateOfferSummary, useCreateOffer } from '@/hooks/useCreateOffer' +import { isPolicyAssetUtxo } from '@/lwk/utxo' import { useLwk } from '@/providers/lwk/useLwk' import { useWallet } from '@/providers/wallet/useWallet' -import { - buildIssuanceFactoryWitness, - loadIssuanceFactoryProgram, -} from '@/simplicity/issuance-factory/program' -import { - buildDerivedLendingOfferProgramParams, - buildPendingOfferMetadata, - loadLendingProgram, -} from '@/simplicity/lending/program' -import { loadScriptAuthProgram } from '@/simplicity/script-auth/program' -import { bytesToHex, hexToBytes, isHexStringOfByteLength, normalizeHex } from '@/utils/hex' +import { isHexStringOfByteLength, normalizeHex } from '@/utils/hex' -import { useTxConfirmations } from './helpers' +import { formatCollateralUtxoOption, useTxConfirmations } from './helpers' +import { TxResult } from './TxResult' const integerStringSchema = (label: string) => zod.string().trim().regex(/^\d+$/, `${label} must be an integer`) @@ -76,7 +52,6 @@ const createOfferFormSchema = zod.object({ }) type CreateOfferForm = zod.input -type ParsedCreateOfferForm = zod.output type CreateOfferFormField = keyof CreateOfferForm type CreateOfferTextFieldProps = Omit< ComponentProps, @@ -107,15 +82,6 @@ const createOfferFormResolver: Resolver = async values => { } } -interface CreateOfferSummary { - inputs: Record - outputs: Record - assetIds: Record - scripts: Record - offerParameters: Record - metadataOpReturnHex: string -} - interface BroadcastState { busy: boolean error: string | null @@ -133,12 +99,6 @@ const DEFAULT_COLLATERAL_AMOUNT = '3000' const DEFAULT_PRINCIPAL_AMOUNT = '10000' const DEFAULT_INTEREST_RATE_BPS = '1000' const DEFAULT_LOAN_DURATION_BLOCKS = '144' -const ISSUING_UTXOS_COUNT = 2 -const REISSUANCE_FLAGS = 0n -const REISSUANCE_TOKEN_AMOUNT = 0n -const NFT_AMOUNT = 1n -const DEFAULT_FEE_RATE = 100 -const DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY = 30_000 const INITIAL_STATE: BroadcastState = { busy: false, @@ -162,15 +122,8 @@ const EMPTY_FORM: CreateOfferForm = { export default function CreateOfferDemo() { const { lwkNetwork } = useLwk() - const { - connectionStatus, - getReceiveAddress, - getWalletUtxos, - getWollet, - getXOnlyPublicKey, - signPset, - syncWallet, - } = useWallet() + const { connectionStatus, getWalletUtxos, syncWallet } = useWallet() + const { createOffer } = useCreateOffer() const { control, handleSubmit } = useForm({ defaultValues: EMPTY_FORM, mode: 'onSubmit', @@ -221,329 +174,20 @@ export default function CreateOfferDemo() { return () => clearTimeout(timeoutId) }, [refreshWalletUtxos]) - const createOffer = async (formValues: CreateOfferForm) => { + const onSubmit = async (formValues: CreateOfferForm) => { setState(current => ({ ...current, busy: true, error: null, summary: null, txid: null })) - - let createStage = 'initializing' try { - createStage = 'parse and validate form' - const parsedForm = parseCreateOfferForm(formValues) - - createStage = 'get x-only public key' - const key = await getXOnlyPublicKey() - if (!key) { - throw new Error('Missing x-only public key') - } - - createStage = 'get receive address' - const receiveAddressString = await getReceiveAddress() - if (!receiveAddressString) { - throw new Error('Missing receive address') - } - - createStage = 'get wollet' - const wollet = await getWollet() - if (!wollet) { - throw new Error('Wallet not connected') - } - - createStage = 'sync wallet and load UTXOs' - await syncWallet() - const walletUtxos = await getWalletUtxos() - setWalletUtxos(walletUtxos) - const factoryAuthUtxo = walletUtxos.find( - utxo => utxoToOutpointString(utxo) === parsedForm.factoryAuthOutpoint, - ) - const collateralUtxo = requireWalletUtxo( - walletUtxos, - parsedForm.collateralOutpoint, - 'Collateral', - ) - - createStage = 'prepare validated form values' - const factoryAsset = AssetId.fromString(parsedForm.factoryAssetId) - const principalAsset = AssetId.fromString(parsedForm.principalAssetId) - const protocolFeeKeeperAsset = AssetId.fromString(parsedForm.protocolFeeKeeperAssetId) - const policyAsset = lwkNetwork.policyAsset() - const factoryAssetString = factoryAsset.toString() - const principalAssetString = principalAsset.toString() - const protocolFeeKeeperAssetString = protocolFeeKeeperAsset.toString() - const policyAssetString = policyAsset.toString() - - if (factoryAuthUtxo) { - assertWalletUtxoAssetAndAmount( - factoryAuthUtxo, - factoryAssetString, - NFT_AMOUNT, - 'FactoryAuth', - ) - } - assertWalletUtxoAssetAndAmount( - collateralUtxo, - policyAssetString, - parsedForm.collateralAmount, - 'Collateral', - ) - - const factoryAuthOutpointString = parsedForm.factoryAuthOutpoint - const issuanceFactoryOutpointString = parsedForm.issuanceFactoryOutpoint - const collateralOutpointString = parsedForm.collateralOutpoint - const issuanceFactoryOutpoint = new OutPoint(issuanceFactoryOutpointString) - const collateralOutpoint = new OutPoint(collateralOutpointString) - - createStage = 'prepare addresses and IssuanceFactory external UTXO' - const receiveAddressExplicitString = Address.parse(receiveAddressString, lwkNetwork) - .toUnconfidential() - .toString() - const issuanceFactoryProgram = loadIssuanceFactoryProgram({ - issuingUtxosCount: ISSUING_UTXOS_COUNT, - reissuanceFlags: REISSUANCE_FLAGS, - }) - const issuanceFactoryAddress = issuanceFactoryProgram.createP2trAddress(key, lwkNetwork) - const issuanceFactoryAddressString = issuanceFactoryAddress.toString() - - const issuanceFactoryTx = Transaction.fromBytes( - await fetchTxRaw(issuanceFactoryOutpoint.txid().toString()), - ) - const issuanceFactoryTxOut = issuanceFactoryTx.outputs[issuanceFactoryOutpoint.vout()] - if (!issuanceFactoryTxOut) { - throw new Error('IssuanceFactory transaction does not have the selected output') - } - - const issuanceFactoryExternalUtxo = new ExternalUtxo( - issuanceFactoryOutpoint.vout(), - issuanceFactoryTx, - TxOutSecrets.fromExplicit(AssetId.fromString(factoryAssetString), NFT_AMOUNT), - DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, - true, - ) - const factoryAuthExternalUtxo = factoryAuthUtxo - ? null - : await buildExplicitExternalUtxo( - factoryAuthOutpointString, - factoryAssetString, - NFT_AMOUNT, - 'FactoryAuth', - ) - - const borrowerNftAsset = assetIdFromIssuance( - issuanceFactoryOutpoint, - ContractHash.fromBytes(new Uint8Array(32)), - ) - const lenderNftAsset = assetIdFromIssuance( - collateralOutpoint, - ContractHash.fromBytes(new Uint8Array(32)), - ) - const borrowerNftAssetString = borrowerNftAsset.toString() - const lenderNftAssetString = lenderNftAsset.toString() - const currentBlockHeight = await fetchLatestBlockHeight() - const loanDurationBlocks = parsedForm.loanDurationBlocks - const offerParameters = { - collateralAmount: parsedForm.collateralAmount, - principalAmount: parsedForm.principalAmount, - principalInterestRate: parsedForm.principalInterestRate, - loanExpirationTime: currentBlockHeight + loanDurationBlocks, + const result = createOfferFormSchema.safeParse(formValues) + if (!result.success) { + throw new Error(result.error.issues.map(issue => issue.message).join('; ')) } - - createStage = 'compile lending and ScriptAuth programs' - const derivedLendingParams = buildDerivedLendingOfferProgramParams( - { - collateralAssetId: AssetId.fromString(policyAssetString).toBytes(), - principalAssetId: AssetId.fromString(principalAssetString).toBytes(), - borrowerNftAssetId: AssetId.fromString(borrowerNftAssetString).toBytes(), - lenderNftAssetId: AssetId.fromString(lenderNftAssetString).toBytes(), - protocolFeeKeeperAssetId: AssetId.fromString(protocolFeeKeeperAssetString).toBytes(), - offerParameters, - }, - key, - lwkNetwork, - ) - const lendingProgram = loadLendingProgram(derivedLendingParams) - const lendingAddress = lendingProgram.createP2trAddress(key, lwkNetwork) - const lendingAddressString = lendingAddress.toString() - const lendingScriptPubkeyHex = bytesToHex(lendingAddress.scriptPubkey().bytes()) - const lendingScriptHash = hexToBytes(new Script(lendingScriptPubkeyHex).jet_sha256_hex()) - const lenderNftScriptAuthProgram = loadScriptAuthProgram(lendingScriptHash) - const lenderNftScriptAuthAddress = lenderNftScriptAuthProgram.createP2trAddress( - key, - lwkNetwork, - ) - const lenderNftScriptAuthAddressString = lenderNftScriptAuthAddress.toString() - const metadata = await buildPendingOfferMetadata({ - principalAssetId: AssetId.fromString(principalAssetString).toBytes(), - offerParameters, - }) - - createStage = 'TxBuilder.new' - let txBuilder = new TxBuilder(lwkNetwork) - - createStage = 'TxBuilder.feeRate' - txBuilder = txBuilder.feeRate(DEFAULT_FEE_RATE) - - createStage = 'TxBuilder.setInputOrder' - txBuilder = txBuilder.setInputOrder([ - new OutPoint(factoryAuthOutpointString), - new OutPoint(issuanceFactoryOutpointString), - new OutPoint(collateralOutpointString), - ]) - - const externalUtxos = factoryAuthExternalUtxo - ? [factoryAuthExternalUtxo, issuanceFactoryExternalUtxo] - : [issuanceFactoryExternalUtxo] - - createStage = 'TxBuilder.addExternalUtxos covenant/explicit inputs' - txBuilder = txBuilder.addExternalUtxos(externalUtxos) - - createStage = 'TxBuilder.addExplicitRecipient FactoryAuth back to user' - txBuilder = txBuilder.addExplicitRecipient( - new Address(receiveAddressExplicitString), // tex1… explicit addresses not supported by Address.parse - NFT_AMOUNT, - AssetId.fromString(factoryAssetString), - ) - - createStage = 'TxBuilder.addExplicitRecipient IssuanceFactory covenant' - txBuilder = txBuilder.addExplicitRecipient( - new Address(issuanceFactoryAddressString), - NFT_AMOUNT, - AssetId.fromString(factoryAssetString), - ) - - createStage = 'TxBuilder.issueAssetToRecipients Borrower NFT to user' - txBuilder = txBuilder.issueAssetToRecipients( - [IssuanceRecipient.fromAddress(NFT_AMOUNT, new Address(receiveAddressExplicitString))], - REISSUANCE_TOKEN_AMOUNT, - null, - null, - new OutPoint(issuanceFactoryOutpointString), - ) - - createStage = 'TxBuilder.issueAssetToRecipients Lender NFT to ScriptAuth' - txBuilder = txBuilder.issueAssetToRecipients( - [IssuanceRecipient.fromAddress(NFT_AMOUNT, new Address(lenderNftScriptAuthAddressString))], - REISSUANCE_TOKEN_AMOUNT, - null, - null, - new OutPoint(collateralOutpointString), - ) - - createStage = 'TxBuilder.addExplicitScriptOutput metadata OP_RETURN' - txBuilder = txBuilder.addExplicitScriptOutput( - Script.newOpReturn(metadata), - 0n, - AssetId.fromString(policyAssetString), - ) - - createStage = 'TxBuilder.addExplicitScriptOutput Lending covenant collateral' - txBuilder = txBuilder.addExplicitScriptOutput( - new Script(lendingScriptPubkeyHex), - offerParameters.collateralAmount, - AssetId.fromString(policyAssetString), - ) - - createStage = 'TxBuilder.finish' - const pset = txBuilder.finish(wollet) - - createStage = 'sign offer PSET' - const signedPset = await signPset(pset) - - createStage = 'finalize wallet inputs' - const finalizedWalletPset = wollet.finalize(signedPset) - - createStage = 'load previous txouts for Simplicity finalize' - const finalizationFactoryAuthOutpoint = new OutPoint(factoryAuthOutpointString) - const finalizationCollateralOutpoint = new OutPoint(collateralOutpointString) - const factoryAuthTx = Transaction.fromBytes( - await fetchTxRaw(finalizationFactoryAuthOutpoint.txid().toString()), - ) - const factoryAuthTxOut = factoryAuthTx.outputs[finalizationFactoryAuthOutpoint.vout()] - if (!factoryAuthTxOut) { - throw new Error('FactoryAuth transaction does not have the selected output') - } - - const collateralTx = Transaction.fromBytes( - await fetchTxRaw(finalizationCollateralOutpoint.txid().toString()), - ) - const collateralTxOut = collateralTx.outputs[finalizationCollateralOutpoint.vout()] - if (!collateralTxOut) { - throw new Error('Collateral transaction does not have the selected output') - } - - createStage = 'finalize IssuanceFactory covenant input' - const txWithWalletWitnesses = finalizedWalletPset.extractTx() - - const finalizedTx = issuanceFactoryProgram.finalizeTransaction( - txWithWalletWitnesses, - key, - [factoryAuthTxOut, issuanceFactoryTxOut, collateralTxOut], - 1, - buildIssuanceFactoryWitness({ - branch: 'IssueAssets', - outputIndex: 0, - }), - lwkNetwork, - SimplicityLogLevel.Trace, - ) - - createStage = 'broadcast transaction' - const txid = await broadcastTx(finalizedTx.toString()) - - setState({ - busy: false, - error: null, - summary: { - inputs: { - '0 FactoryAuth': parsedForm.factoryAuthOutpoint, - '1 IssuanceFactory covenant': parsedForm.issuanceFactoryOutpoint, - '2 Collateral LBTC': parsedForm.collateralOutpoint, - }, - outputs: { - '0 FactoryAuth back to user': receiveAddressExplicitString, - '1 IssuanceFactory back to covenant': issuanceFactoryAddressString, - '2 Borrower NFT to user': receiveAddressExplicitString, - '3 Lender NFT to ScriptAuth': lenderNftScriptAuthAddressString, - '4 Metadata OP_RETURN': bytesToHex(Script.newOpReturn(metadata).bytes()), - '5 Lending covenant': lendingAddressString, - }, - assetIds: { - factoryAssetId: factoryAssetString, - collateralAssetId: policyAssetString, - principalAssetId: principalAssetString, - borrowerNftAssetId: borrowerNftAssetString, - lenderNftAssetId: lenderNftAssetString, - protocolFeeKeeperAssetId: protocolFeeKeeperAssetString, - }, - scripts: { - lendingScriptHash: bytesToHex(lendingScriptHash), - lenderVaultCovHash: bytesToHex(derivedLendingParams.lenderVaultCovHash), - finalizedLenderVaultCovHash: bytesToHex( - derivedLendingParams.finalizedLenderVaultCovHash, - ), - protocolFeeVaultCovHash: bytesToHex(derivedLendingParams.protocolFeeVaultCovHash), - finalizedProtocolFeeVaultCovHash: bytesToHex( - derivedLendingParams.finalizedProtocolFeeVaultCovHash, - ), - principalOutputScriptHash: bytesToHex(derivedLendingParams.principalOutputScriptHash), - }, - offerParameters: { - collateralAmount: offerParameters.collateralAmount.toString(), - principalAmount: offerParameters.principalAmount.toString(), - principalInterestRate: offerParameters.principalInterestRate.toString(), - currentBlockHeight: currentBlockHeight.toString(), - loanDurationBlocks: loanDurationBlocks.toString(), - loanExpirationTime: offerParameters.loanExpirationTime.toString(), - }, - metadataOpReturnHex: bytesToHex(Script.newOpReturn(metadata).bytes()), - }, - txid, - }) + const { txid, summary } = await createOffer(result.data) + setState({ busy: false, error: null, txid, summary }) } catch (err) { - const errorMessage = err instanceof Error ? err.message : String(err) - const errorBody = - err instanceof Error && 'body' in err && typeof err.body === 'string' ? err.body : null setState(current => ({ ...current, busy: false, - error: `${createStage}: ${errorMessage}${errorBody ? ` | response: ${errorBody}` : ''}`, + error: err instanceof Error ? err.message : String(err), })) } } @@ -656,7 +300,7 @@ export default function CreateOfferDemo() { isDisabled={connectionStatus !== 'ready'} isPending={state.busy} loadingText='Creating offer...' - onPress={() => void handleSubmit(createOffer)()} + onPress={() => void handleSubmit(onSubmit)()} > Create Offer @@ -664,112 +308,12 @@ export default function CreateOfferDemo() { {state.error ?

Create: {state.error}

: null} - +
) } - -function BroadcastResult({ - confirmations, - summary, - txid, -}: { - confirmations: number | null - summary: CreateOfferSummary | null - txid: string | null -}) { - if (!txid) { - return null - } - - return ( -
-
Offer Created
- - {txid} - -

- {confirmations !== null - ? `${confirmations} confirmation${confirmations === 1 ? '' : 's'}` - : 'Waiting for confirmation...'} -

- {summary ? ( -
-          {JSON.stringify(summary, null, 2)}
-        
- ) : null} -
- ) -} - -function formatCollateralUtxoOption(utxo: WalletTxOut): UiSelectOption { - const outpoint = utxoToOutpointString(utxo) - const height = utxo.height() - const status = height === undefined ? 'mempool' : `height ${height}` - return { - id: outpoint, - label: `${outpoint} | ${utxo.unblinded().value().toString()} sats | ${status}`, - } -} - -async function buildExplicitExternalUtxo( - outpointString: string, - assetIdString: string, - amount: bigint, - label: string, -): Promise { - const outpoint = new OutPoint(outpointString) - const tx = Transaction.fromBytes(await fetchTxRaw(outpoint.txid().toString())) - const txOut = tx.outputs[outpoint.vout()] - if (!txOut) { - throw new Error(`${label} transaction does not have the selected output`) - } - - return new ExternalUtxo( - outpoint.vout(), - tx, - TxOutSecrets.fromExplicit(AssetId.fromString(assetIdString), amount), - DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, - true, - ) -} - -function parseCreateOfferForm(form: CreateOfferForm): ParsedCreateOfferForm { - const result = createOfferFormSchema.safeParse(form) - if (!result.success) { - throw new Error(result.error.issues.map(issue => issue.message).join('; ')) - } - return result.data -} - -function requireWalletUtxo( - walletUtxos: WalletTxOut[], - outpoint: string, - label: string, -): WalletTxOut { - const utxo = walletUtxos.find(utxo => utxoToOutpointString(utxo) === outpoint.trim()) - if (!utxo) { - throw new Error(`${label} wallet UTXO not found`) - } - return utxo -} - -function assertWalletUtxoAssetAndAmount( - utxo: WalletTxOut, - assetId: string, - minAmount: bigint, - label: string, -) { - const unblinded = utxo.unblinded() - if (unblinded.asset().toString() !== assetId) { - throw new Error(`${label} UTXO has unexpected asset ${unblinded.asset().toString()}`) - } - if (unblinded.value() < minAmount) { - throw new Error(`${label} UTXO amount is lower than ${minAmount.toString()}`) - } -} diff --git a/web-v2/src/pages/Dashboard/Demos/helpers.ts b/web-v2/src/pages/Dashboard/Demos/helpers.ts index 83ee678..6a49e6a 100644 --- a/web-v2/src/pages/Dashboard/Demos/helpers.ts +++ b/web-v2/src/pages/Dashboard/Demos/helpers.ts @@ -134,3 +134,13 @@ export function removeScriptAuthState(authOutpoint: string): void { localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(filteredStates)) } + +export function formatCollateralUtxoOption(utxo: WalletTxOut): { id: string; label: string } { + const outpoint = utxoToOutpointString(utxo) + const height = utxo.height() + const status = height === undefined ? 'mempool' : `height ${height}` + return { + id: outpoint, + label: `${outpoint} | ${utxo.unblinded().value().toString()} sats | ${status}`, + } +} From d1773b7a77b45db4e5ac093635e029c6e91d4bf1 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 5 Jun 2026 18:47:11 +0300 Subject: [PATCH 13/16] fix: ensure wallet is connected before syncing --- web-v2/src/hooks/useCreateOffer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web-v2/src/hooks/useCreateOffer.ts b/web-v2/src/hooks/useCreateOffer.ts index f7bc887..09b6969 100644 --- a/web-v2/src/hooks/useCreateOffer.ts +++ b/web-v2/src/hooks/useCreateOffer.ts @@ -82,7 +82,6 @@ export function useCreateOffer() { stage = 'get wollet' const wollet = await getWollet() - if (!wollet) throw new Error('Wallet not connected') stage = 'sync wallet and load UTXOs' await syncWallet() From 91c67e7a3064a79def00d1a326f314a4f5d6514d Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Fri, 5 Jun 2026 18:49:58 +0300 Subject: [PATCH 14/16] chore: remove unused script auth helper functions --- web-v2/src/simplicity/script-auth/helpers.ts | 135 ------------------- 1 file changed, 135 deletions(-) delete mode 100644 web-v2/src/simplicity/script-auth/helpers.ts diff --git a/web-v2/src/simplicity/script-auth/helpers.ts b/web-v2/src/simplicity/script-auth/helpers.ts deleted file mode 100644 index 6b887d3..0000000 --- a/web-v2/src/simplicity/script-auth/helpers.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { AssetId, WalletTxOut } from 'lwk_web' -import { useEffect, useState } from 'react' - -import { fetchTxConfirmations } from '@/api/esplora/methods' -import { getPolicyAssetUtxos, outpointToString } from '@/lwk/utxo' - -export interface DemoScriptAuthInputSelection { - authUtxo: WalletTxOut - fundingUtxo: WalletTxOut -} - -/** - * Temporary input selection strategy used by - * ScriptAuth smoke tests and demo flows. - * - * Production covenant creation will use - * explicit auth/funding UTXO selection. - */ -export function selectDemoScriptAuthInputs( - walletUtxos: WalletTxOut[], - policyAsset: AssetId | string, - feeReserve: bigint, -): DemoScriptAuthInputSelection { - const lbtcUtxos = getPolicyAssetUtxos(walletUtxos, policyAsset) - - const fundingUtxo = lbtcUtxos - .filter(utxo => utxo.unblinded().value() > feeReserve) - .sort((a, b) => { - const aValue = a.unblinded().value() - const bValue = b.unblinded().value() - if (aValue === bValue) return 0 - return bValue > aValue ? 1 : -1 - })[0] - - if (!fundingUtxo) { - throw new Error('Need a wallet L-BTC UTXO larger than the fee reserve to fund ScriptAuth') - } - - const fundingOutpoint = outpointToString(fundingUtxo) - const authUtxo = lbtcUtxos.find(utxo => outpointToString(utxo) !== fundingOutpoint) - - if (!authUtxo) { - throw new Error('Need a second wallet L-BTC UTXO to use as the ScriptAuth auth input') - } - - return { authUtxo, fundingUtxo } -} - -export interface SavedScriptAuthState { - authOutpoint: string - scriptHashHex: string - fundingTxid: string -} - -export function latestScriptAuthState() { - const states = getScriptAuthStates() - return states[states.length - 1] ?? null -} - -export function useTxConfirmations(txid: string | null): number | null { - const [confirmedTx, setConfirmedTx] = useState<{ - confirmations: number - txid: string - } | null>(null) - - useEffect(() => { - if (!txid) { - return - } - - let cancelled = false - let intervalId: ReturnType | null = null - - const poll = async () => { - try { - const nextConfirmations = await fetchTxConfirmations(txid) - - if (cancelled) { - return - } - - if (nextConfirmations !== null && nextConfirmations >= 1) { - setConfirmedTx({ confirmations: nextConfirmations, txid }) - if (intervalId) { - clearInterval(intervalId) - } - } - } catch (err) { - console.warn(err) - } - } - - poll() - intervalId = setInterval(() => { - poll() - }, 15_000) - - return () => { - cancelled = true - if (intervalId) { - clearInterval(intervalId) - } - } - }, [txid]) - - return confirmedTx?.txid === txid ? confirmedTx.confirmations : null -} - -const SCRIPT_AUTH_STORAGE_KEY = 'script-auth-covenants' - -export function getScriptAuthStates(): SavedScriptAuthState[] { - const raw = localStorage.getItem(SCRIPT_AUTH_STORAGE_KEY) - - if (!raw) { - return [] - } - - return JSON.parse(raw) -} - -export function saveScriptAuthState(state: SavedScriptAuthState): void { - const existingStates = getScriptAuthStates() - - existingStates.push(state) - - localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(existingStates)) -} - -export function removeScriptAuthState(authOutpoint: string): void { - const existingStates = getScriptAuthStates() - - const filteredStates = existingStates.filter(state => state.authOutpoint !== authOutpoint) - - localStorage.setItem(SCRIPT_AUTH_STORAGE_KEY, JSON.stringify(filteredStates)) -} From 9cc8c714ad079a2241002437a9e36cce3bccadf0 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Mon, 8 Jun 2026 13:12:43 +0300 Subject: [PATCH 15/16] refactor: replace numeric types with typed wrappers for safety --- web-v2/src/api/esplora/utils.ts | 3 +- web-v2/src/hooks/useBorrowerAccount.ts | 11 +- web-v2/src/hooks/useCreateOffer.ts | 173 +++++++++--------- .../pages/Dashboard/Demos/CreateOfferDemo.tsx | 11 +- .../Demos/ScriptAuthCovenantDemo.tsx | 11 +- .../simplicity/asset-auth-vault/program.ts | 82 ++------- web-v2/src/simplicity/asset-auth/program.ts | 16 +- .../simplicity/issuance-factory/program.ts | 19 +- web-v2/src/simplicity/lending/program.ts | 170 ++++++----------- web-v2/src/simplicity/lending/utils.ts | 14 ++ web-v2/src/simplicity/script-auth/program.ts | 7 +- web-v2/src/utils/sha256.ts | 3 + web-v2/src/utils/uint.ts | 52 ++++-- 13 files changed, 249 insertions(+), 323 deletions(-) create mode 100644 web-v2/src/simplicity/lending/utils.ts create mode 100644 web-v2/src/utils/sha256.ts diff --git a/web-v2/src/api/esplora/utils.ts b/web-v2/src/api/esplora/utils.ts index 5124f16..f347745 100644 --- a/web-v2/src/api/esplora/utils.ts +++ b/web-v2/src/api/esplora/utils.ts @@ -1,5 +1,6 @@ import { env } from '@/constants/env' import { bytesToHex, hexToBytes } from '@/utils/hex' +import { sha256 } from '@/utils/sha256' function buildExplorerUrl(path: string): string { return `${env.VITE_ESPLORA_BASE_URL}${path}` @@ -19,7 +20,7 @@ export function getAddressExplorerUrl(address: string): string { export async function hashScriptPubkeyHex(scriptPubkeyHex: string): Promise { const scriptBytes = hexToBytes(scriptPubkeyHex) - const digestBuffer = await crypto.subtle.digest('SHA-256', scriptBytes) + const digestBuffer = await sha256(scriptBytes) return new Uint8Array(digestBuffer) } diff --git a/web-v2/src/hooks/useBorrowerAccount.ts b/web-v2/src/hooks/useBorrowerAccount.ts index fa8ae32..8982565 100644 --- a/web-v2/src/hooks/useBorrowerAccount.ts +++ b/web-v2/src/hooks/useBorrowerAccount.ts @@ -13,6 +13,8 @@ import { useLwk } from '@/providers/lwk/useLwk' import { useWallet } from '@/providers/wallet/useWallet' import { loadIssuanceFactoryProgram } from '@/simplicity/issuance-factory/program' import { bytesToHex } from '@/utils/hex' +import { sha256 } from '@/utils/sha256' +import { toUint8, toUint64 } from '@/utils/uint' const FEE_RESERVE = 10_000n const ISSUING_UTXOS_COUNT = 2 @@ -61,8 +63,8 @@ export function useBorrowerAccount() { const fundingOutpoint = utxoToOutpointString(feeUtxo) const receiveAddress = Address.parse(receiveAddressString, lwkNetwork).toUnconfidential() const issuanceFactoryProgram = loadIssuanceFactoryProgram({ - issuingUtxosCount: ISSUING_UTXOS_COUNT, - reissuanceFlags: REISSUANCE_FLAGS, + issuingUtxosCount: toUint8(ISSUING_UTXOS_COUNT, 'issuingUtxosCount'), + reissuanceFlags: toUint64(REISSUANCE_FLAGS, 'reissuanceFlags'), }) const factoryAddress = issuanceFactoryProgram.createP2trAddress(xOnlyPublicKey, lwkNetwork) const issuedAssetId = assetIdFromIssuance(feeUtxo.outpoint(), emptyContractHash()) @@ -114,10 +116,7 @@ function emptyContractHash(): ContractHash { async function buildMetadata(): Promise { const { sources } = await import('virtual:simplicity-sources') - const hash = await crypto.subtle.digest( - 'SHA-256', - new TextEncoder().encode(sources.issuance_factory), - ) + const hash = await sha256(new TextEncoder().encode(sources.issuance_factory)) const programId = new Uint8Array(hash).slice(0, 4) const data = new Uint8Array(13) data.set(programId, 0) diff --git a/web-v2/src/hooks/useCreateOffer.ts b/web-v2/src/hooks/useCreateOffer.ts index 09b6969..1ae2740 100644 --- a/web-v2/src/hooks/useCreateOffer.ts +++ b/web-v2/src/hooks/useCreateOffer.ts @@ -29,6 +29,7 @@ import { } from '@/simplicity/lending/program' import { loadScriptAuthProgram } from '@/simplicity/script-auth/program' import { bytesToHex, hexToBytes } from '@/utils/hex' +import { toBytes32, toUint8, toUint16, toUint32, toUint64 } from '@/utils/uint' const ISSUING_UTXOS_COUNT = 2 const REISSUANCE_FLAGS = 0n @@ -50,18 +51,16 @@ export interface CreateOfferParams { protocolFeeKeeperAssetId: string } -export interface CreateOfferSummary { - inputs: Record - outputs: Record - assetIds: Record - scripts: Record - offerParameters: Record - metadataOpReturnHex: string -} - export interface CreateOfferResult { txid: string - summary: CreateOfferSummary + summary: { + inputs: Record + outputs: Record + assetIds: Record + scripts: Record + offerParameters: Record + metadataOpReturnHex: string + } } export function useCreateOffer() { @@ -72,17 +71,15 @@ export function useCreateOffer() { const createOffer = async (params: CreateOfferParams): Promise => { let stage = 'initializing' try { - stage = 'get x-only public key' - const xOnlyPublicKey = await getXOnlyPublicKey() + stage = 'load wallet context' + const [xOnlyPublicKey, receiveAddressString, wollet] = await Promise.all([ + getXOnlyPublicKey(), + getReceiveAddress(), + getWollet(), + ]) if (!xOnlyPublicKey) throw new Error('Missing x-only public key') - - stage = 'get receive address' - const receiveAddressString = await getReceiveAddress() if (!receiveAddressString) throw new Error('Missing receive address') - stage = 'get wollet' - const wollet = await getWollet() - stage = 'sync wallet and load UTXOs' await syncWallet() const walletUtxos = await getWalletUtxos() @@ -92,14 +89,10 @@ export function useCreateOffer() { const collateralUtxo = requireWalletUtxo(walletUtxos, params.collateralOutpoint, 'Collateral') stage = 'prepare validated params' - const factoryAsset = AssetId.fromString(params.factoryAssetId) - const principalAsset = AssetId.fromString(params.principalAssetId) - const protocolFeeKeeperAsset = AssetId.fromString(params.protocolFeeKeeperAssetId) - const policyAsset = lwkNetwork.policyAsset() - const factoryAssetString = factoryAsset.toString() - const principalAssetString = principalAsset.toString() - const protocolFeeKeeperAssetString = protocolFeeKeeperAsset.toString() - const policyAssetString = policyAsset.toString() + const factoryAssetString = params.factoryAssetId + const principalAssetString = params.principalAssetId + const protocolFeeKeeperAssetString = params.protocolFeeKeeperAssetId + const policyAssetString = lwkNetwork.policyAsset().toString() if (factoryAuthUtxo) { assertWalletUtxoAssetAndAmount( @@ -116,16 +109,38 @@ export function useCreateOffer() { 'Collateral', ) + const factoryAuthOutpoint = new OutPoint(params.factoryAuthOutpoint) const issuanceFactoryOutpoint = new OutPoint(params.issuanceFactoryOutpoint) const collateralOutpoint = new OutPoint(params.collateralOutpoint) - stage = 'prepare addresses and IssuanceFactory external UTXO' + stage = 'load transaction context' + const [factoryAuthTxBytes, issuanceFactoryTxBytes, collateralTxBytes, currentBlockHeight] = + await Promise.all([ + fetchTxRaw(factoryAuthOutpoint.txid().toString()), + fetchTxRaw(issuanceFactoryOutpoint.txid().toString()), + fetchTxRaw(collateralOutpoint.txid().toString()), + fetchLatestBlockHeight(), + ]) + const factoryAuthTx = Transaction.fromBytes(factoryAuthTxBytes) + const issuanceFactoryTx = Transaction.fromBytes(issuanceFactoryTxBytes) + const collateralTx = Transaction.fromBytes(collateralTxBytes) + const factoryAuthTxOut = factoryAuthTx.outputs[factoryAuthOutpoint.vout()] + const issuanceFactoryTxOut = issuanceFactoryTx.outputs[issuanceFactoryOutpoint.vout()] + const collateralTxOut = collateralTx.outputs[collateralOutpoint.vout()] + if (!factoryAuthTxOut) + throw new Error('FactoryAuth transaction does not have the selected output') + if (!issuanceFactoryTxOut) + throw new Error('IssuanceFactory transaction does not have the selected output') + if (!collateralTxOut) + throw new Error('Collateral transaction does not have the selected output') + + stage = 'prepare addresses and external UTXOs' const receiveAddressExplicitString = Address.parse(receiveAddressString, lwkNetwork) .toUnconfidential() .toString() const issuanceFactoryProgram = loadIssuanceFactoryProgram({ - issuingUtxosCount: ISSUING_UTXOS_COUNT, - reissuanceFlags: REISSUANCE_FLAGS, + issuingUtxosCount: toUint8(ISSUING_UTXOS_COUNT, 'issuingUtxosCount'), + reissuanceFlags: toUint64(REISSUANCE_FLAGS, 'reissuanceFlags'), }) const issuanceFactoryAddress = issuanceFactoryProgram.createP2trAddress( xOnlyPublicKey, @@ -133,13 +148,6 @@ export function useCreateOffer() { ) const issuanceFactoryAddressString = issuanceFactoryAddress.toString() - const issuanceFactoryTx = Transaction.fromBytes( - await fetchTxRaw(issuanceFactoryOutpoint.txid().toString()), - ) - const issuanceFactoryTxOut = issuanceFactoryTx.outputs[issuanceFactoryOutpoint.vout()] - if (!issuanceFactoryTxOut) - throw new Error('IssuanceFactory transaction does not have the selected output') - const issuanceFactoryExternalUtxo = new ExternalUtxo( issuanceFactoryOutpoint.vout(), issuanceFactoryTx, @@ -149,11 +157,12 @@ export function useCreateOffer() { ) const factoryAuthExternalUtxo = factoryAuthUtxo ? null - : await buildExplicitExternalUtxo( - params.factoryAuthOutpoint, - factoryAssetString, - NFT_AMOUNT, - 'FactoryAuth', + : new ExternalUtxo( + factoryAuthOutpoint.vout(), + factoryAuthTx, + TxOutSecrets.fromExplicit(AssetId.fromString(factoryAssetString), NFT_AMOUNT), + DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, + true, ) const borrowerNftAsset = assetIdFromIssuance( @@ -166,23 +175,36 @@ export function useCreateOffer() { ) const borrowerNftAssetString = borrowerNftAsset.toString() const lenderNftAssetString = lenderNftAsset.toString() - const currentBlockHeight = await fetchLatestBlockHeight() const loanDurationBlocks = params.loanDurationBlocks const offerParameters = { - collateralAmount: params.collateralAmount, - principalAmount: params.principalAmount, - principalInterestRate: params.principalInterestRate, - loanExpirationTime: currentBlockHeight + loanDurationBlocks, + collateralAmount: toUint64(params.collateralAmount, 'collateralAmount'), + principalAmount: toUint64(params.principalAmount, 'principalAmount'), + principalInterestRate: toUint16(params.principalInterestRate, 'principalInterestRate'), + loanExpirationTime: toUint32(currentBlockHeight + loanDurationBlocks, 'loanExpirationTime'), } + const collateralAssetId = toBytes32( + AssetId.fromString(policyAssetString).toBytes(), + 'collateralAssetId', + ) + const principalAssetId = toBytes32( + AssetId.fromString(principalAssetString).toBytes(), + 'principalAssetId', + ) + const borrowerNftAssetId = toBytes32(borrowerNftAsset.toBytes(), 'borrowerNftAssetId') + const lenderNftAssetId = toBytes32(lenderNftAsset.toBytes(), 'lenderNftAssetId') + const protocolFeeKeeperAssetId = toBytes32( + AssetId.fromString(protocolFeeKeeperAssetString).toBytes(), + 'protocolFeeKeeperAssetId', + ) stage = 'compile lending and ScriptAuth programs' const derivedLendingParams = buildDerivedLendingOfferProgramParams( { - collateralAssetId: AssetId.fromString(policyAssetString).toBytes(), - principalAssetId: AssetId.fromString(principalAssetString).toBytes(), - borrowerNftAssetId: AssetId.fromString(borrowerNftAssetString).toBytes(), - lenderNftAssetId: AssetId.fromString(lenderNftAssetString).toBytes(), - protocolFeeKeeperAssetId: AssetId.fromString(protocolFeeKeeperAssetString).toBytes(), + collateralAssetId, + principalAssetId, + borrowerNftAssetId, + lenderNftAssetId, + protocolFeeKeeperAssetId, offerParameters, }, xOnlyPublicKey, @@ -192,7 +214,10 @@ export function useCreateOffer() { const lendingAddress = lendingProgram.createP2trAddress(xOnlyPublicKey, lwkNetwork) const lendingAddressString = lendingAddress.toString() const lendingScriptPubkeyHex = bytesToHex(lendingAddress.scriptPubkey().bytes()) - const lendingScriptHash = hexToBytes(new Script(lendingScriptPubkeyHex).jet_sha256_hex()) + const lendingScriptHash = toBytes32( + hexToBytes(new Script(lendingScriptPubkeyHex).jet_sha256_hex()), + 'lendingScriptHash', + ) const lenderNftScriptAuthProgram = loadScriptAuthProgram(lendingScriptHash) const lenderNftScriptAuthAddress = lenderNftScriptAuthProgram.createP2trAddress( xOnlyPublicKey, @@ -200,7 +225,7 @@ export function useCreateOffer() { ) const lenderNftScriptAuthAddressString = lenderNftScriptAuthAddress.toString() const metadata = await buildPendingOfferMetadata({ - principalAssetId: AssetId.fromString(principalAssetString).toBytes(), + principalAssetId, offerParameters, }) @@ -279,23 +304,6 @@ export function useCreateOffer() { stage = 'finalize wallet inputs' const finalizedWalletPset = wollet.finalize(signedPset) - stage = 'load previous txouts for Simplicity finalize' - const finalizationFactoryAuthOutpoint = new OutPoint(params.factoryAuthOutpoint) - const finalizationCollateralOutpoint = new OutPoint(params.collateralOutpoint) - const factoryAuthTx = Transaction.fromBytes( - await fetchTxRaw(finalizationFactoryAuthOutpoint.txid().toString()), - ) - const factoryAuthTxOut = factoryAuthTx.outputs[finalizationFactoryAuthOutpoint.vout()] - if (!factoryAuthTxOut) - throw new Error('FactoryAuth transaction does not have the selected output') - - const collateralTx = Transaction.fromBytes( - await fetchTxRaw(finalizationCollateralOutpoint.txid().toString()), - ) - const collateralTxOut = collateralTx.outputs[finalizationCollateralOutpoint.vout()] - if (!collateralTxOut) - throw new Error('Collateral transaction does not have the selected output') - stage = 'finalize IssuanceFactory covenant input' const txWithWalletWitnesses = finalizedWalletPset.extractTx() const finalizedTx = issuanceFactoryProgram.finalizeTransaction( @@ -303,7 +311,10 @@ export function useCreateOffer() { xOnlyPublicKey, [factoryAuthTxOut, issuanceFactoryTxOut, collateralTxOut], 1, - buildIssuanceFactoryWitness({ branch: 'IssueAssets', outputIndex: 0 }), + buildIssuanceFactoryWitness({ + branch: 'IssueAssets', + outputIndex: toUint32(0, 'outputIndex'), + }), lwkNetwork, SimplicityLogLevel.Trace, ) @@ -369,26 +380,6 @@ export function useCreateOffer() { return { createOffer } } -async function buildExplicitExternalUtxo( - outpointString: string, - assetIdString: string, - amount: bigint, - label: string, -): Promise { - const outpoint = new OutPoint(outpointString) - const tx = Transaction.fromBytes(await fetchTxRaw(outpoint.txid().toString())) - const txOut = tx.outputs[outpoint.vout()] - if (!txOut) throw new Error(`${label} transaction does not have the selected output`) - - return new ExternalUtxo( - outpoint.vout(), - tx, - TxOutSecrets.fromExplicit(AssetId.fromString(assetIdString), amount), - DEFAULT_EXTERNAL_UTXO_MAX_WEIGHT_TO_SATISFY, - true, - ) -} - function requireWalletUtxo( walletUtxos: WalletTxOut[], outpoint: string, diff --git a/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx b/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx index 6670955..066d4bb 100644 --- a/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx +++ b/web-v2/src/pages/Dashboard/Demos/CreateOfferDemo.tsx @@ -6,7 +6,7 @@ import { z as zod } from 'zod' import { UiButton } from '@/components/ui/UiButton' import { UiSelect } from '@/components/ui/UiSelect' import { UiTextField } from '@/components/ui/UiTextField' -import { type CreateOfferSummary, useCreateOffer } from '@/hooks/useCreateOffer' +import { type CreateOfferResult, useCreateOffer } from '@/hooks/useCreateOffer' import { isPolicyAssetUtxo } from '@/lwk/utxo' import { useLwk } from '@/providers/lwk/useLwk' import { useWallet } from '@/providers/wallet/useWallet' @@ -85,7 +85,7 @@ const createOfferFormResolver: Resolver = async values => { interface BroadcastState { busy: boolean error: string | null - summary: CreateOfferSummary | null + summary: CreateOfferResult['summary'] | null txid: string | null } @@ -108,8 +108,11 @@ const INITIAL_STATE: BroadcastState = { } const EMPTY_FORM: CreateOfferForm = { - factoryAuthOutpoint: '6ad27ff9c22819f98a8f08cf777d370c0b30549bee6f8cd86c3522ab203b5018:0', - issuanceFactoryOutpoint: '6ad27ff9c22819f98a8f08cf777d370c0b30549bee6f8cd86c3522ab203b5018:1', + // Update factoryAuthOutpoint and issuanceFactoryOutpoint + // with the last create offer tx:0 and tx:1 + // every time you use VITE_DEBUG_MNEMONIC + factoryAuthOutpoint: '822ffbd963cf31e6e20b4ba0bf99312c27b00ace07499aa330f7559cec1ea1e7:0', + issuanceFactoryOutpoint: '822ffbd963cf31e6e20b4ba0bf99312c27b00ace07499aa330f7559cec1ea1e7:1', factoryAssetId: 'a61ab9c860e382039cb5df9386319887c1a3e60116f5fcb7ad3497b430806d18', collateralOutpoint: '', collateralAmount: DEFAULT_COLLATERAL_AMOUNT, diff --git a/web-v2/src/pages/Dashboard/Demos/ScriptAuthCovenantDemo.tsx b/web-v2/src/pages/Dashboard/Demos/ScriptAuthCovenantDemo.tsx index 3fccd29..6f727f4 100644 --- a/web-v2/src/pages/Dashboard/Demos/ScriptAuthCovenantDemo.tsx +++ b/web-v2/src/pages/Dashboard/Demos/ScriptAuthCovenantDemo.tsx @@ -27,6 +27,7 @@ import { useLwk } from '@/providers/lwk/useLwk' import { useWallet } from '@/providers/wallet/useWallet' import { buildScriptAuthWitness, loadScriptAuthProgram } from '@/simplicity/script-auth/program' import { hexToBytes } from '@/utils/hex' +import { toBytes32, toUint32 } from '@/utils/uint' interface FundingSummary { covenantAddress: string @@ -108,7 +109,9 @@ export default function ScriptAuthCovenantDemo() { DEFAULT_SCRIPT_AUTH_FEE_RESERVE, ) const scriptHashHex = authUtxo.scriptPubkey().jet_sha256_hex() - const scriptAuthProgram = loadScriptAuthProgram(hexToBytes(scriptHashHex)) + const scriptAuthProgram = loadScriptAuthProgram( + toBytes32(hexToBytes(scriptHashHex), 'scriptHash'), + ) const scriptAuthAddress = scriptAuthProgram.createP2trAddress(key, lwkNetwork) const covenantAddress = scriptAuthAddress.toString() @@ -173,7 +176,9 @@ export default function ScriptAuthCovenantDemo() { } const { scriptHashHex } = scriptAuthState - const scriptAuthProgram = loadScriptAuthProgram(hexToBytes(scriptHashHex)) + const scriptAuthProgram = loadScriptAuthProgram( + toBytes32(hexToBytes(scriptHashHex), 'scriptHash'), + ) const key = xOnlyPublicKey ?? (await getXOnlyPublicKey()) if (!key) { throw new Error('Missing x-only public key') @@ -258,7 +263,7 @@ export default function ScriptAuthCovenantDemo() { key, [covenantTxOut, authTxOut], COVENANT_INPUT_INDEX, - buildScriptAuthWitness(AUTH_INPUT_INDEX), + buildScriptAuthWitness(toUint32(AUTH_INPUT_INDEX, 'authInputIndex')), lwkNetwork, SimplicityLogLevel.Trace, ) diff --git a/web-v2/src/simplicity/asset-auth-vault/program.ts b/web-v2/src/simplicity/asset-auth-vault/program.ts index d1581cb..28844fe 100644 --- a/web-v2/src/simplicity/asset-auth-vault/program.ts +++ b/web-v2/src/simplicity/asset-auth-vault/program.ts @@ -8,7 +8,7 @@ import { import { sources } from 'virtual:simplicity-sources' import { bytes32ToHex } from '@/utils/hex' -import { assertBytes32, assertUint32, assertUint64 } from '@/utils/uint' +import type { Bytes32, Uint32, Uint64 } from '@/utils/uint' const ARGUMENTS = { VAULT_ASSET_ID: 'VAULT_ASSET_ID', @@ -26,11 +26,11 @@ const WITNESS = { } as const export interface AssetAuthVaultProgramParams { - vaultAssetId: Uint8Array - keeperAuthAssetId: Uint8Array - supplierAuthAssetId: Uint8Array - keeperAuthAssetAmount: bigint - finalizedVaultCovHash: Uint8Array + vaultAssetId: Bytes32 + keeperAuthAssetId: Bytes32 + supplierAuthAssetId: Bytes32 + keeperAuthAssetAmount: Uint64 + finalizedVaultCovHash: Bytes32 isActive: boolean withKeeperAssetBurn: boolean withSupplierAssetBurn: boolean @@ -39,29 +39,29 @@ export interface AssetAuthVaultProgramParams { export type AssetAuthVaultWitnessParams = | { branch: 'WithdrawAll' - inputKeeperIndex: number - outputKeeperIndex: number + inputKeeperIndex: Uint32 + outputKeeperIndex: Uint32 } | { branch: 'WithdrawPart' - inputKeeperIndex: number - outputKeeperIndex: number - vaultOutputIndex: number - amountToWithdraw: bigint + inputKeeperIndex: Uint32 + outputKeeperIndex: Uint32 + vaultOutputIndex: Uint32 + amountToWithdraw: Uint64 } | { branch: 'Supply' - inputSupplierIndex: number - outputSupplierIndex: number - vaultOutputIndex: number - amountToSupply: bigint + inputSupplierIndex: Uint32 + outputSupplierIndex: Uint32 + vaultOutputIndex: Uint32 + amountToSupply: Uint64 } | { branch: 'FinalSupply' - inputSupplierIndex: number - outputSupplierIndex: number - finalizedVaultOutputIndex: number - amountToSupply: bigint + inputSupplierIndex: Uint32 + outputSupplierIndex: Uint32 + finalizedVaultOutputIndex: Uint32 + amountToSupply: Uint64 } export function loadAssetAuthVaultProgram(params: AssetAuthVaultProgramParams): SimplicityProgram { @@ -82,13 +82,6 @@ export function buildAssetAuthVaultArguments( withSupplierAssetBurn, } = params - assertBytes32(vaultAssetId, 'vaultAssetId') - assertBytes32(keeperAuthAssetId, 'keeperAuthAssetId') - assertBytes32(supplierAuthAssetId, 'supplierAuthAssetId') - assertBytes32(finalizedVaultCovHash, 'finalizedVaultCovHash') - - assertUint64(keeperAuthAssetAmount, 'keeperAuthAssetAmount') - return new SimplicityArguments() .addValue( ARGUMENTS.VAULT_ASSET_ID, @@ -121,27 +114,6 @@ export function buildAssetAuthVaultArguments( ) } -export function buildFinalizedAssetAuthVaultParams( - params: Omit, -): AssetAuthVaultProgramParams { - return { - ...params, - finalizedVaultCovHash: new Uint8Array(32), - isActive: false, - } -} - -export function buildActiveAssetAuthVaultParams( - finalizedParams: AssetAuthVaultProgramParams, - finalizedVaultCovHash: Uint8Array, -): AssetAuthVaultProgramParams { - return { - ...finalizedParams, - finalizedVaultCovHash, - isActive: true, - } -} - export function buildAssetAuthVaultWitness( params: AssetAuthVaultWitnessParams, ): SimplicityWitnessValues { @@ -158,26 +130,12 @@ export function buildAssetAuthVaultWitness( function buildAssetAuthVaultPathExpression(params: AssetAuthVaultWitnessParams): string { switch (params.branch) { case 'WithdrawAll': - assertUint32(params.inputKeeperIndex, 'inputKeeperIndex') - assertUint32(params.outputKeeperIndex, 'outputKeeperIndex') return `Left(Left((${params.inputKeeperIndex}, ${params.outputKeeperIndex})))` case 'WithdrawPart': - assertUint32(params.inputKeeperIndex, 'inputKeeperIndex') - assertUint32(params.outputKeeperIndex, 'outputKeeperIndex') - assertUint32(params.vaultOutputIndex, 'vaultOutputIndex') - assertUint64(params.amountToWithdraw, 'amountToWithdraw') return `Left(Right((${params.inputKeeperIndex}, ${params.outputKeeperIndex}, ${params.vaultOutputIndex}, ${params.amountToWithdraw})))` case 'Supply': - assertUint32(params.inputSupplierIndex, 'inputSupplierIndex') - assertUint32(params.outputSupplierIndex, 'outputSupplierIndex') - assertUint32(params.vaultOutputIndex, 'vaultOutputIndex') - assertUint64(params.amountToSupply, 'amountToSupply') return `Right(Left((${params.inputSupplierIndex}, ${params.outputSupplierIndex}, ${params.vaultOutputIndex}, ${params.amountToSupply})))` case 'FinalSupply': - assertUint32(params.inputSupplierIndex, 'inputSupplierIndex') - assertUint32(params.outputSupplierIndex, 'outputSupplierIndex') - assertUint32(params.finalizedVaultOutputIndex, 'finalizedVaultOutputIndex') - assertUint64(params.amountToSupply, 'amountToSupply') return `Right(Right((${params.inputSupplierIndex}, ${params.outputSupplierIndex}, ${params.finalizedVaultOutputIndex}, ${params.amountToSupply})))` } } diff --git a/web-v2/src/simplicity/asset-auth/program.ts b/web-v2/src/simplicity/asset-auth/program.ts index 81e5fab..1b93a96 100644 --- a/web-v2/src/simplicity/asset-auth/program.ts +++ b/web-v2/src/simplicity/asset-auth/program.ts @@ -7,7 +7,7 @@ import { import { sources } from 'virtual:simplicity-sources' import { bytes32ToHex } from '@/utils/hex' -import { assertBytes32, assertUint32, assertUint64 } from '@/utils/uint' +import type { Bytes32, Uint32, Uint64 } from '@/utils/uint' const ARGUMENTS = { ASSET_ID: 'ASSET_ID', @@ -21,14 +21,14 @@ const WITNESS = { } as const export interface AssetAuthProgramParams { - assetId: Uint8Array - assetAmount: bigint + assetId: Bytes32 + assetAmount: Uint64 withAssetBurn: boolean } export interface AssetAuthWitnessParams { - inputAssetIndex: number - outputAssetIndex: number + inputAssetIndex: Uint32 + outputAssetIndex: Uint32 } export function loadAssetAuthProgram(params: AssetAuthProgramParams): SimplicityProgram { @@ -36,9 +36,6 @@ export function loadAssetAuthProgram(params: AssetAuthProgramParams): Simplicity } export function buildAssetAuthArguments(params: AssetAuthProgramParams): SimplicityArguments { - assertBytes32(params.assetId, 'assetId') - assertUint64(params.assetAmount, 'assetAmount') - return new SimplicityArguments() .addValue(ARGUMENTS.ASSET_ID, SimplicityTypedValue.fromU256Hex(bytes32ToHex(params.assetId))) .addValue(ARGUMENTS.ASSET_AMOUNT, SimplicityTypedValue.fromU64(params.assetAmount)) @@ -46,9 +43,6 @@ export function buildAssetAuthArguments(params: AssetAuthProgramParams): Simplic } export function buildAssetAuthWitness(params: AssetAuthWitnessParams): SimplicityWitnessValues { - assertUint32(params.inputAssetIndex, 'inputAssetIndex') - assertUint32(params.outputAssetIndex, 'outputAssetIndex') - return new SimplicityWitnessValues() .addValue(WITNESS.INPUT_ASSET_INDEX, SimplicityTypedValue.fromU32(params.inputAssetIndex)) .addValue(WITNESS.OUTPUT_ASSET_INDEX, SimplicityTypedValue.fromU32(params.outputAssetIndex)) diff --git a/web-v2/src/simplicity/issuance-factory/program.ts b/web-v2/src/simplicity/issuance-factory/program.ts index dd68c24..7fce329 100644 --- a/web-v2/src/simplicity/issuance-factory/program.ts +++ b/web-v2/src/simplicity/issuance-factory/program.ts @@ -7,7 +7,7 @@ import { } from 'lwk_web' import { sources } from 'virtual:simplicity-sources' -import { isUint8, isUint32, isUint64 } from '@/utils/uint' +import type { Uint8, Uint32, Uint64 } from '@/utils/uint' const ARGUMENTS = { ISSUING_UTXOS_COUNT: 'ISSUING_UTXOS_COUNT', @@ -19,13 +19,13 @@ const WITNESS = { } as const export interface IssuanceFactoryProgramParams { - issuingUtxosCount: number - reissuanceFlags: bigint + issuingUtxosCount: Uint8 + reissuanceFlags: Uint64 } export interface IssuanceFactoryWitnessParams { branch: 'IssueAssets' | 'RemoveFactory' - outputIndex: number + outputIndex: Uint32 } export function loadIssuanceFactoryProgram( @@ -37,13 +37,6 @@ export function loadIssuanceFactoryProgram( export function buildIssuanceFactoryArguments( params: IssuanceFactoryProgramParams, ): SimplicityArguments { - if (!isUint8(params.issuingUtxosCount)) { - throw new Error('issuingUtxosCount must fit into u8') - } - if (!isUint64(params.reissuanceFlags)) { - throw new Error('reissuanceFlags must fit into u64') - } - return new SimplicityArguments() .addValue(ARGUMENTS.ISSUING_UTXOS_COUNT, SimplicityTypedValue.fromU8(params.issuingUtxosCount)) .addValue(ARGUMENTS.REISSUANCE_FLAGS, SimplicityTypedValue.fromU64(params.reissuanceFlags)) @@ -52,10 +45,6 @@ export function buildIssuanceFactoryArguments( export function buildIssuanceFactoryWitness( params: IssuanceFactoryWitnessParams, ): SimplicityWitnessValues { - if (!isUint32(params.outputIndex)) { - throw new Error('outputIndex must fit into u32') - } - const pathType = SimplicityType.fromString('Either') const pathExpression = params.branch === 'IssueAssets' ? `Left(${params.outputIndex})` : `Right(${params.outputIndex})` diff --git a/web-v2/src/simplicity/lending/program.ts b/web-v2/src/simplicity/lending/program.ts index a75a272..b63b0e0 100644 --- a/web-v2/src/simplicity/lending/program.ts +++ b/web-v2/src/simplicity/lending/program.ts @@ -9,15 +9,22 @@ import { } from 'lwk_web' import { sources } from 'virtual:simplicity-sources' -import { type AssetAuthProgramParams, loadAssetAuthProgram } from '@/simplicity/asset-auth/program' +import { loadAssetAuthProgram } from '@/simplicity/asset-auth/program' import { type AssetAuthVaultProgramParams, - buildActiveAssetAuthVaultParams, - buildFinalizedAssetAuthVaultParams, loadAssetAuthVaultProgram, } from '@/simplicity/asset-auth-vault/program' +import { getTotalAmountToRepay } from '@/simplicity/lending/utils' import { bytes32ToHex, hexToBytes } from '@/utils/hex' -import { isUint16, isUint32, isUint64 } from '@/utils/uint' +import { sha256 } from '@/utils/sha256' +import { + type Bytes32, + toBytes32, + toUint64, + type Uint16, + type Uint32, + type Uint64, +} from '@/utils/uint' const ARGUMENTS = { COLLATERAL_ASSET_ID: 'COLLATERAL_ASSET_ID', @@ -40,49 +47,38 @@ const WITNESS = { } as const export interface OfferParameters { - collateralAmount: bigint - principalAmount: bigint - principalInterestRate: number - loanExpirationTime: number + collateralAmount: Uint64 + principalAmount: Uint64 + principalInterestRate: Uint16 + loanExpirationTime: Uint32 } export interface LendingOfferProgramParams { - collateralAssetId: Uint8Array - principalAssetId: Uint8Array - borrowerNftAssetId: Uint8Array - lenderNftAssetId: Uint8Array - protocolFeeKeeperAssetId: Uint8Array + collateralAssetId: Bytes32 + principalAssetId: Bytes32 + borrowerNftAssetId: Bytes32 + lenderNftAssetId: Bytes32 + protocolFeeKeeperAssetId: Bytes32 offerParameters: OfferParameters - lenderVaultCovHash: Uint8Array - finalizedLenderVaultCovHash: Uint8Array - protocolFeeVaultCovHash: Uint8Array - finalizedProtocolFeeVaultCovHash: Uint8Array - principalOutputScriptHash: Uint8Array + lenderVaultCovHash: Bytes32 + finalizedLenderVaultCovHash: Bytes32 + protocolFeeVaultCovHash: Bytes32 + finalizedProtocolFeeVaultCovHash: Bytes32 + principalOutputScriptHash: Bytes32 } export type LendingOfferWitnessParams = | { branch: 'OfferAcceptance' } | { branch: 'OfferCancellation' } - | { branch: 'PartialRepayment'; currentDebt: bigint; amountToRepay: bigint } - | { branch: 'FullRepayment'; currentDebt: bigint } - | { branch: 'Liquidation'; currentDebt: bigint } + | { branch: 'PartialRepayment'; currentDebt: Uint64; amountToRepay: Uint64 } + | { branch: 'FullRepayment'; currentDebt: Uint64 } + | { branch: 'Liquidation'; currentDebt: Uint64 } export function loadLendingProgram(params: LendingOfferProgramParams): SimplicityProgram { return SimplicityProgram.load(sources.lending, buildLendingArguments(params)) } export function buildLendingArguments(params: LendingOfferProgramParams): SimplicityArguments { - assertBytes32(params.collateralAssetId, 'collateralAssetId') - assertBytes32(params.principalAssetId, 'principalAssetId') - assertBytes32(params.borrowerNftAssetId, 'borrowerNftAssetId') - assertBytes32(params.lenderNftAssetId, 'lenderNftAssetId') - assertBytes32(params.lenderVaultCovHash, 'lenderVaultCovHash') - assertBytes32(params.finalizedLenderVaultCovHash, 'finalizedLenderVaultCovHash') - assertBytes32(params.protocolFeeVaultCovHash, 'protocolFeeVaultCovHash') - assertBytes32(params.finalizedProtocolFeeVaultCovHash, 'finalizedProtocolFeeVaultCovHash') - assertBytes32(params.principalOutputScriptHash, 'principalOutputScriptHash') - assertOfferParameters(params.offerParameters) - return new SimplicityArguments() .addValue( ARGUMENTS.COLLATERAL_ASSET_ID, @@ -138,30 +134,22 @@ export function buildLendingArguments(params: LendingOfferProgramParams): Simpli ) } -export function buildPrincipalOutputAssetAuthParams( - params: Pick, -): AssetAuthProgramParams { - return { - assetId: params.borrowerNftAssetId, - assetAmount: getTotalAmountToRepay(params.offerParameters), - withAssetBurn: false, - } -} - export function buildFinalizedLenderVaultParams( params: Pick< LendingOfferProgramParams, 'principalAssetId' | 'lenderNftAssetId' | 'borrowerNftAssetId' >, ): AssetAuthVaultProgramParams { - return buildFinalizedAssetAuthVaultParams({ + return { vaultAssetId: params.principalAssetId, keeperAuthAssetId: params.lenderNftAssetId, - keeperAuthAssetAmount: 1n, + keeperAuthAssetAmount: toUint64(1n), withKeeperAssetBurn: true, supplierAuthAssetId: params.borrowerNftAssetId, withSupplierAssetBurn: true, - }) + finalizedVaultCovHash: toBytes32(new Uint8Array(32)), + isActive: false, + } } export function buildFinalizedProtocolFeeVaultParams( @@ -170,14 +158,16 @@ export function buildFinalizedProtocolFeeVaultParams( 'principalAssetId' | 'protocolFeeKeeperAssetId' | 'borrowerNftAssetId' >, ): AssetAuthVaultProgramParams { - return buildFinalizedAssetAuthVaultParams({ + return { vaultAssetId: params.principalAssetId, keeperAuthAssetId: params.protocolFeeKeeperAssetId, - keeperAuthAssetAmount: 1n, + keeperAuthAssetAmount: toUint64(1n), withKeeperAssetBurn: false, supplierAuthAssetId: params.borrowerNftAssetId, withSupplierAssetBurn: true, - }) + finalizedVaultCovHash: toBytes32(new Uint8Array(32)), + isActive: false, + } } export function buildLendingWitness(params: LendingOfferWitnessParams): SimplicityWitnessValues { @@ -203,7 +193,11 @@ export function buildDerivedLendingOfferProgramParams( internalKey: XOnlyPublicKey, network: Network, ): LendingOfferProgramParams { - const principalOutputAssetAuth = loadAssetAuthProgram(buildPrincipalOutputAssetAuthParams(params)) + const principalOutputAssetAuth = loadAssetAuthProgram({ + assetId: params.borrowerNftAssetId, + assetAmount: getTotalAmountToRepay(params.offerParameters), + withAssetBurn: false, + }) const finalizedLenderVault = loadAssetAuthVaultProgram(buildFinalizedLenderVaultParams(params)) const finalizedProtocolFeeVault = loadAssetAuthVaultProgram( buildFinalizedProtocolFeeVaultParams(params), @@ -218,18 +212,16 @@ export function buildDerivedLendingOfferProgramParams( internalKey, network, ) - const activeLenderVault = loadAssetAuthVaultProgram( - buildActiveAssetAuthVaultParams( - buildFinalizedLenderVaultParams(params), - finalizedLenderVaultCovHash, - ), - ) - const activeProtocolFeeVault = loadAssetAuthVaultProgram( - buildActiveAssetAuthVaultParams( - buildFinalizedProtocolFeeVaultParams(params), - finalizedProtocolFeeVaultCovHash, - ), - ) + const activeLenderVault = loadAssetAuthVaultProgram({ + ...buildFinalizedLenderVaultParams(params), + finalizedVaultCovHash: finalizedLenderVaultCovHash, + isActive: true, + }) + const activeProtocolFeeVault = loadAssetAuthVaultProgram({ + ...buildFinalizedProtocolFeeVaultParams(params), + finalizedVaultCovHash: finalizedProtocolFeeVaultCovHash, + isActive: true, + }) return { ...params, @@ -241,39 +233,24 @@ export function buildDerivedLendingOfferProgramParams( } } -export function getProgramScriptHash( +function getProgramScriptHash( program: SimplicityProgram, internalKey: XOnlyPublicKey, network: Network, -): Uint8Array { - return hexToBytes(program.createP2trAddress(internalKey, network).scriptPubkey().jet_sha256_hex()) -} - -export function getTotalAmountToRepay(params: OfferParameters): bigint { - assertOfferParameters(params) - - return ( - params.principalAmount + - (params.principalAmount * BigInt(params.principalInterestRate)) / 10_000n +): Bytes32 { + return toBytes32( + hexToBytes(program.createP2trAddress(internalKey, network).scriptPubkey().jet_sha256_hex()), + 'programScriptHash', ) } export async function buildPendingOfferMetadata(params: { - principalAssetId: Uint8Array + principalAssetId: Bytes32 offerParameters: Pick< OfferParameters, 'principalAmount' | 'loanExpirationTime' | 'principalInterestRate' > }): Promise { - assertBytes32(params.principalAssetId, 'principalAssetId') - assertUint64(params.offerParameters.principalAmount, 'principalAmount') - if (!isUint32(params.offerParameters.loanExpirationTime)) { - throw new Error('loanExpirationTime must fit into u32') - } - if (!isUint16(params.offerParameters.principalInterestRate)) { - throw new Error('principalInterestRate must fit into u16') - } - const programId = await getLendingProgramId() const data = new Uint8Array(50) const view = new DataView(data.buffer) @@ -286,7 +263,7 @@ export async function buildPendingOfferMetadata(params: { } async function getLendingProgramId(): Promise { - const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(sources.lending)) + const hash = await sha256(new TextEncoder().encode(sources.lending)) return new Uint8Array(hash).slice(0, 4) } @@ -297,37 +274,10 @@ function buildLendingPathExpression(params: LendingOfferWitnessParams): string { case 'OfferCancellation': return 'Left(Right(()))' case 'PartialRepayment': - assertUint64(params.currentDebt, 'currentDebt') - assertUint64(params.amountToRepay, 'amountToRepay') return `Right(Left(Left((${params.currentDebt}, ${params.amountToRepay}))))` case 'FullRepayment': - assertUint64(params.currentDebt, 'currentDebt') return `Right(Left(Right(${params.currentDebt})))` case 'Liquidation': - assertUint64(params.currentDebt, 'currentDebt') return `Right(Right(${params.currentDebt}))` } } - -function assertOfferParameters(params: OfferParameters): void { - assertUint64(params.collateralAmount, 'collateralAmount') - assertUint64(params.principalAmount, 'principalAmount') - if (!isUint16(params.principalInterestRate)) { - throw new Error('principalInterestRate must fit into u16') - } - if (!isUint32(params.loanExpirationTime)) { - throw new Error('loanExpirationTime must fit into u32') - } -} - -function assertBytes32(value: Uint8Array, label: string): void { - if (value.length !== 32) { - throw new Error(`${label} must be 32 bytes`) - } -} - -function assertUint64(value: bigint, label: string): void { - if (!isUint64(value)) { - throw new Error(`${label} must fit into u64`) - } -} diff --git a/web-v2/src/simplicity/lending/utils.ts b/web-v2/src/simplicity/lending/utils.ts new file mode 100644 index 0000000..974d874 --- /dev/null +++ b/web-v2/src/simplicity/lending/utils.ts @@ -0,0 +1,14 @@ +import { toUint64, type Uint16, type Uint64 } from '@/utils/uint' + +interface TotalAmountToRepayParams { + principalAmount: Uint64 + principalInterestRate: Uint16 +} + +export function getTotalAmountToRepay(params: TotalAmountToRepayParams): Uint64 { + return toUint64( + params.principalAmount + + (params.principalAmount * BigInt(params.principalInterestRate)) / 10_000n, + 'totalAmountToRepay', + ) +} diff --git a/web-v2/src/simplicity/script-auth/program.ts b/web-v2/src/simplicity/script-auth/program.ts index a71e4e3..64678ff 100644 --- a/web-v2/src/simplicity/script-auth/program.ts +++ b/web-v2/src/simplicity/script-auth/program.ts @@ -7,6 +7,7 @@ import { import { sources } from 'virtual:simplicity-sources' import { bytes32ToHex } from '@/utils/hex' +import type { Bytes32, Uint32 } from '@/utils/uint' const ARGUMENTS = { SCRIPT_HASH: 'SCRIPT_HASH', @@ -16,18 +17,18 @@ const WITNESS = { INPUT_SCRIPT_INDEX: 'INPUT_SCRIPT_INDEX', } as const -export function loadScriptAuthProgram(scriptHash: Uint8Array): SimplicityProgram { +export function loadScriptAuthProgram(scriptHash: Bytes32): SimplicityProgram { return SimplicityProgram.load(sources.script_auth, buildScriptAuthArguments(scriptHash)) } -export function buildScriptAuthArguments(scriptHash: Uint8Array): SimplicityArguments { +export function buildScriptAuthArguments(scriptHash: Bytes32): SimplicityArguments { return new SimplicityArguments().addValue( ARGUMENTS.SCRIPT_HASH, SimplicityTypedValue.fromU256Hex(bytes32ToHex(scriptHash)), ) } -export function buildScriptAuthWitness(inputScriptIndex: number): SimplicityWitnessValues { +export function buildScriptAuthWitness(inputScriptIndex: Uint32): SimplicityWitnessValues { return new SimplicityWitnessValues().addValue( WITNESS.INPUT_SCRIPT_INDEX, SimplicityTypedValue.fromU32(inputScriptIndex), diff --git a/web-v2/src/utils/sha256.ts b/web-v2/src/utils/sha256.ts new file mode 100644 index 0000000..42341bc --- /dev/null +++ b/web-v2/src/utils/sha256.ts @@ -0,0 +1,3 @@ +export function sha256(data: BufferSource): Promise { + return crypto.subtle.digest('SHA-256', data) +} diff --git a/web-v2/src/utils/uint.ts b/web-v2/src/utils/uint.ts index 089d4e8..98301cf 100644 --- a/web-v2/src/utils/uint.ts +++ b/web-v2/src/utils/uint.ts @@ -3,36 +3,54 @@ const MAX_U16 = 0xffff const MAX_U32 = 0xffff_ffff const MAX_U64 = (1n << 64n) - 1n -export function isUint8(value: number): boolean { - return Number.isInteger(value) && value >= 0 && value <= MAX_U8 -} +declare const bytes32Brand: unique symbol +declare const uint8Brand: unique symbol +declare const uint16Brand: unique symbol +declare const uint32Brand: unique symbol +declare const uint64Brand: unique symbol -export function isUint16(value: number): boolean { - return Number.isInteger(value) && value >= 0 && value <= MAX_U16 -} +export type Bytes32 = Uint8Array & { readonly [bytes32Brand]: never } +export type Uint8 = number & { readonly [uint8Brand]: never } +export type Uint16 = number & { readonly [uint16Brand]: never } +export type Uint32 = number & { readonly [uint32Brand]: never } +export type Uint64 = bigint & { readonly [uint64Brand]: never } -export function isUint32(value: number): boolean { - return Number.isInteger(value) && value >= 0 && value <= MAX_U32 +export function toBytes32(value: Uint8Array, label = 'Value'): Bytes32 { + if (value.length !== 32) { + throw new Error(`${label} must be 32 bytes`) + } + + return value as Bytes32 } -export function isUint64(value: bigint): boolean { - return value >= 0n && value <= MAX_U64 +export function toUint8(value: number, label = 'Value'): Uint8 { + if (!Number.isInteger(value) || value < 0 || value > MAX_U8) { + throw new Error(`${label} must fit into u8`) + } + + return value as Uint8 } -export function assertBytes32(value: Uint8Array, label: string): void { - if (value.length !== 32) { - throw new Error(`${label} must be 32 bytes`) +export function toUint16(value: number, label = 'Value'): Uint16 { + if (!Number.isInteger(value) || value < 0 || value > MAX_U16) { + throw new Error(`${label} must fit into u16`) } + + return value as Uint16 } -export function assertUint32(value: number, label: string): void { - if (!isUint32(value)) { +export function toUint32(value: number, label = 'Value'): Uint32 { + if (!Number.isInteger(value) || value < 0 || value > MAX_U32) { throw new Error(`${label} must fit into u32`) } + + return value as Uint32 } -export function assertUint64(value: bigint, label: string): void { - if (!isUint64(value)) { +export function toUint64(value: bigint, label = 'Value'): Uint64 { + if (value < 0n || value > MAX_U64) { throw new Error(`${label} must fit into u64`) } + + return value as Uint64 } From 647ef534d579c8356d4ae32c0fa69bed4d248234 Mon Sep 17 00:00:00 2001 From: lilbonekit Date: Mon, 8 Jun 2026 13:17:02 +0300 Subject: [PATCH 16/16] refactor: remove export from buildFinalizedLenderVaultParams --- web-v2/src/simplicity/lending/program.ts | 28 +++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/web-v2/src/simplicity/lending/program.ts b/web-v2/src/simplicity/lending/program.ts index b63b0e0..4f761c6 100644 --- a/web-v2/src/simplicity/lending/program.ts +++ b/web-v2/src/simplicity/lending/program.ts @@ -134,7 +134,7 @@ export function buildLendingArguments(params: LendingOfferProgramParams): Simpli ) } -export function buildFinalizedLenderVaultParams( +function buildFinalizedLenderVaultParams( params: Pick< LendingOfferProgramParams, 'principalAssetId' | 'lenderNftAssetId' | 'borrowerNftAssetId' @@ -152,7 +152,7 @@ export function buildFinalizedLenderVaultParams( } } -export function buildFinalizedProtocolFeeVaultParams( +function buildFinalizedProtocolFeeVaultParams( params: Pick< LendingOfferProgramParams, 'principalAssetId' | 'protocolFeeKeeperAssetId' | 'borrowerNftAssetId' @@ -170,17 +170,6 @@ export function buildFinalizedProtocolFeeVaultParams( } } -export function buildLendingWitness(params: LendingOfferWitnessParams): SimplicityWitnessValues { - const pathType = SimplicityType.fromString( - 'Either, Either, u64>>', - ) - - return new SimplicityWitnessValues().addValue( - WITNESS.PATH, - SimplicityTypedValue.parse(buildLendingPathExpression(params), pathType), - ) -} - export function buildDerivedLendingOfferProgramParams( params: Omit< LendingOfferProgramParams, @@ -267,6 +256,19 @@ async function getLendingProgramId(): Promise { return new Uint8Array(hash).slice(0, 4) } +// TODO: Will be used in the offer acceptance, +// cancellation, repayment, and liquidation flows to construct the appropriate witness values for each branch of the program +export function buildLendingWitness(params: LendingOfferWitnessParams): SimplicityWitnessValues { + const pathType = SimplicityType.fromString( + 'Either, Either, u64>>', + ) + + return new SimplicityWitnessValues().addValue( + WITNESS.PATH, + SimplicityTypedValue.parse(buildLendingPathExpression(params), pathType), + ) +} + function buildLendingPathExpression(params: LendingOfferWitnessParams): string { switch (params.branch) { case 'OfferAcceptance':