add monero accounts/addresses, fix rpc auth, fix binary extraction

This commit is contained in:
2026-05-17 10:44:36 +08:00
parent 6b7a773845
commit 1555144400
9 changed files with 1286 additions and 233 deletions
+1
View File
@@ -20,6 +20,7 @@
"electrobun": "^1.18.1",
"ethers": "^6.16.0",
"mode-watcher": "^1.1.0",
"monero-ts": "^0.11.8",
"sv-router": "latest",
"viem": "^2.48.11"
},
+182 -1
View File
@@ -21,6 +21,13 @@ import { db } from './lib/db'
import { join } from 'path'
import { getENV } from './lib/get-env'
import { getClient, getTransactionDetails } from './lib/viem'
import {
isBinaryInstalled,
downloadBinary,
getBinaryPath,
getBinaryDir,
MoneroWalletManager,
} from './lib/monero'
const env = getENV()
@@ -169,6 +176,71 @@ type RPC = {
}
response: void
}
moneroBinaryStatus: {
params: {}
response: {
installed: boolean
downloading: boolean
error?: string
}
}
moneroDownloadBinary: {
params: {}
response: void
}
moneroStart: {
params: { daemonAddress?: string }
response: {
running: boolean
walletOpen: boolean
walletName?: string
connected: boolean
}
}
moneroStop: {
params: {}
response: void
}
moneroCreateWallet: {
params: { name: string; password: string }
response: { mnemonic: string; address: string }
}
moneroRestoreWallet: {
params: { name: string; password: string; mnemonic: string; restoreHeight?: number }
response: { address: string }
}
moneroOpenWallet: {
params: { name: string; password: string }
response: void
}
moneroGetBalance: {
params: {}
response: {
balance: string
unlocked: string
address: string
height: number
}
}
moneroGetTransactions: {
params: {}
response: Array<{
hash: string
amount: string
timestamp: string
direction: 'in' | 'out'
height: number
}>
}
moneroWalletStatus: {
params: {}
response: {
running: boolean
walletOpen: boolean
walletName?: string
connected: boolean
}
}
}
messages: {}
}>
@@ -274,8 +346,11 @@ function mapTransfer(t: any): TxEntry {
}
}
let moneroManager: MoneroWalletManager | null = null
let moneroDownloading = false
const rpc = BrowserView.defineRPC<RPC>({
maxRequestTime: 10000,
maxRequestTime: 120000,
handlers: {
requests: {
getSecret: async ({ name, service }) => {
@@ -441,6 +516,112 @@ const rpc = BrowserView.defineRPC<RPC>({
return null
}
},
moneroBinaryStatus: async () => {
const status = { installed: isBinaryInstalled(), downloading: moneroDownloading }
console.log('[rpc] moneroBinaryStatus:', status)
return status
},
moneroDownloadBinary: async () => {
console.log('[rpc] moneroDownloadBinary: starting download...')
moneroDownloading = true
try {
await downloadBinary()
console.log('[rpc] moneroDownloadBinary: complete')
} catch (e) {
console.log('[rpc] moneroDownloadBinary failed:', e)
throw e
} finally {
moneroDownloading = false
}
},
moneroStart: async ({ daemonAddress }) => {
console.log('[rpc] moneroStart:', { daemonAddress })
if (!moneroManager) {
moneroManager = new MoneroWalletManager()
}
try {
await moneroManager.start(daemonAddress)
} catch (e) {
console.log('[rpc] moneroStart error:', e)
return { running: false, walletOpen: false, connected: false }
}
const connected = await moneroManager.isConnected()
console.log('[rpc] moneroStart complete, connected:', connected)
return { running: true, walletOpen: false, connected }
},
moneroStop: async () => {
console.log('[rpc] moneroStop')
if (moneroManager) {
await moneroManager.stop()
moneroManager = null
}
},
moneroCreateWallet: async ({ name, password }) => {
console.log('[rpc] moneroCreateWallet:', name)
if (!moneroManager) throw new Error('Monero wallet RPC not started')
const result = await moneroManager.createWallet(name, password)
console.log('[rpc] moneroCreateWallet complete')
return result
},
moneroRestoreWallet: async ({ name, password, mnemonic, restoreHeight }) => {
console.log('[rpc] moneroRestoreWallet:', name)
if (!moneroManager) throw new Error('Monero wallet RPC not started')
await moneroManager.restoreWallet(name, password, mnemonic, restoreHeight)
const address = await moneroManager.getAddress()
console.log('[rpc] moneroRestoreWallet complete, address:', address)
return { address }
},
moneroOpenWallet: async ({ name, password }) => {
console.log('[rpc] moneroOpenWallet:', name)
if (!moneroManager) throw new Error('Monero wallet RPC not started')
await moneroManager.openWallet(name, password)
},
moneroGetBalance: async () => {
console.log('[rpc] moneroGetBalance')
if (!moneroManager) throw new Error('Monero wallet RPC not started')
const { balance, unlocked } = await moneroManager.getBalance()
const address = await moneroManager.getAddress()
const height = await moneroManager.getHeight()
const daemonHeight = await moneroManager.getDaemonHeight()
console.log('[rpc] balance:', { balance: balance.toString(), unlocked: unlocked.toString(), address, height, daemonHeight })
return {
balance: balance.toString(),
unlocked: unlocked.toString(),
address,
height,
daemonHeight,
}
},
moneroGetTransactions: async () => {
console.log('[rpc] moneroGetTransactions')
if (!moneroManager) throw new Error('Monero wallet RPC not started')
const txs = await moneroManager.getTransactions()
const result = (txs || []).map((tx: any) => ({
hash: tx.hash,
amount: tx.amount?.toString() ?? '0',
timestamp: String(tx.timestamp ?? 0),
direction: tx.direction === 'in' ? 'in' as const : 'out' as const,
height: tx.height ?? 0,
}))
console.log(`[rpc] moneroGetTransactions: ${result.length} txs`)
return result
},
moneroWalletStatus: async () => {
if (!moneroManager) {
console.log('[rpc] moneroWalletStatus: not running')
return { running: false, walletOpen: false, connected: false }
}
const connected = await moneroManager.isConnected()
console.log('[rpc] moneroWalletStatus: running, connected:', connected)
return { running: true, walletOpen: true, connected }
},
moneroGetAccounts: async () => {
console.log('[rpc] moneroGetAccounts')
if (!moneroManager) throw new Error('Monero wallet RPC not started')
const accounts = await moneroManager.getAccounts()
console.log(`[rpc] moneroGetAccounts: ${accounts.length} accounts`)
return accounts
},
},
messages: {},
},
+110
View File
@@ -0,0 +1,110 @@
import { existsSync, mkdirSync } from 'fs'
import { join } from 'path'
import envPaths from 'env-paths'
import { getENV } from '../get-env'
const env = getENV()
const { data } = envPaths(env.appName, { suffix: '' })
export const MONERO_VERSION = '0.18.5.0'
const PLATFORM_MAP: Record<string, Record<string, string>> = {
darwin: { arm64: 'mac-armv8', x64: 'mac-x64' },
linux: { arm64: 'linux-armv8', x64: 'linux-x64' },
}
function getPlatform(): string {
const os = PLATFORM_MAP[process.platform]
if (!os)
throw new Error(
`Unsupported platform: ${process.platform} ${process.arch}`,
)
const arch = os[process.arch]
if (!arch)
throw new Error(
`Unsupported arch: ${process.arch} on ${process.platform}`,
)
return arch
}
export function getDownloadUrl(): string {
return `https://downloads.getmonero.org/cli/monero-${getPlatform()}-v${MONERO_VERSION}.tar.bz2`
}
export function getBinaryDir(): string {
const dir = join(data, 'monero-bin')
mkdirSync(dir, { recursive: true })
return dir
}
export function getBinaryPath(): string {
return join(getBinaryDir(), 'monero-wallet-rpc')
}
export function getWalletDir(): string {
const dir = join(data, 'monero-wallets')
mkdirSync(dir, { recursive: true })
return dir
}
export function isBinaryInstalled(): boolean {
return existsSync(getBinaryPath())
}
export async function downloadBinary(): Promise<void> {
const url = getDownloadUrl()
const archivePath = join(
getBinaryDir(),
`monero-v${MONERO_VERSION}.tar.bz2`,
)
console.log(`Downloading Monero CLI ${MONERO_VERSION}...`)
console.log(`URL: ${url}`)
const response = await fetch(url)
if (!response.ok)
throw new Error(
`Download failed: ${response.status} ${response.statusText}`,
)
const total = Number(response.headers.get('content-length') ?? 0)
console.log(`Download size: ${(total / 1024 / 1024).toFixed(1)} MB`)
const reader = response.body?.getReader()
if (!reader) throw new Error('No response body')
const chunks: Uint8Array[] = []
let received = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks.push(value)
received += value.length
const pct = total ? ((received / total) * 100).toFixed(0) : '?'
console.log(
`Download progress: ${pct}% (${(received / 1024 / 1024).toFixed(1)} MB)`,
)
}
const buffer = new Uint8Array(received)
let offset = 0
for (const chunk of chunks) {
buffer.set(chunk, offset)
offset += chunk.length
}
await Bun.write(archivePath, buffer)
console.log(`Downloaded to ${archivePath}`)
console.log('Extracting...')
const result = Bun.spawnSync(['tar', '-xjf', archivePath, '--strip-components=1', '-C', getBinaryDir()])
if (result.exitCode !== 0) throw new Error(`Extraction failed: ${result.stderr.toString()}`)
Bun.spawnSync(['rm', archivePath])
const binaryDst = getBinaryPath()
if (!existsSync(binaryDst)) throw new Error(`monero-wallet-rpc not found after extraction`)
Bun.spawnSync(['chmod', '+x', binaryDst])
console.log(`Installed monero-wallet-rpc at ${binaryDst}`)
}
+2
View File
@@ -0,0 +1,2 @@
export * from './binary'
export { MoneroWalletManager } from './wallet'
+225
View File
@@ -0,0 +1,225 @@
import { MoneroWalletRpc, MoneroRpcConnection, connectToWalletRpc, connectToDaemonRpc } from 'monero-ts'
import { getBinaryPath, getWalletDir } from './binary'
import { existsSync } from 'fs'
const DEFAULT_DAEMON = 'xmr-node.cakewallet.com:18081'
export class MoneroWalletManager {
private wallet: MoneroWalletRpc | null = null
private process: Bun.Subprocess | null = null
private rpcPort: number
private rpcUser: string
private rpcPassword: string
private daemonAddress: string = DEFAULT_DAEMON
constructor() {
this.rpcPort = 28084
this.rpcUser = 'koins'
this.rpcPassword = crypto.randomUUID()
}
private get rpcUrl() {
return `http://127.0.0.1:${this.rpcPort}`
}
private get rpcConnection(): MoneroRpcConnection {
return new MoneroRpcConnection({
uri: this.rpcUrl,
username: this.rpcUser,
password: this.rpcPassword,
})
}
async start(daemonAddress: string = DEFAULT_DAEMON) {
if (!existsSync(getBinaryPath())) throw new Error('monero-wallet-rpc not installed. Call downloadBinary() first.')
this.daemonAddress = daemonAddress
console.log(`[monero] starting wallet-rpc process...`)
console.log(`[monero] daemon address: ${this.daemonAddress}`)
console.log(`[monero] wallet dir: ${getWalletDir()}`)
const cmd = [
getBinaryPath(),
'--daemon-address', this.daemonAddress,
'--rpc-bind-port', String(this.rpcPort),
'--rpc-bind-ip', '127.0.0.1',
'--wallet-dir', getWalletDir(),
'--rpc-login', `${this.rpcUser}:${this.rpcPassword}`,
'--trusted-daemon',
]
console.log(`[monero] spawning: ${cmd.join(' ')}`)
this.process = Bun.spawn(cmd, {
env: { ...process.env, LANG: 'en_US.UTF-8' },
stdout: 'pipe',
stderr: 'pipe',
onExit(_proc, exitCode, _signalCode, error) {
console.log(`[monero] wallet-rpc process exited with code ${exitCode}`, error?.message ?? '')
},
})
console.log(`[monero] wallet-rpc pid: ${this.process.pid}`)
const stdout = this.process.stdout.getReader()
;(async () => {
while (true) {
const { done, value } = await stdout.read()
if (done) break
console.log(`[monero:stdout] ${new TextDecoder().decode(value)}`)
}
})()
const stderr = this.process.stderr.getReader()
;(async () => {
while (true) {
const { done, value } = await stderr.read()
if (done) break
console.log(`[monero:stderr] ${new TextDecoder().decode(value)}`)
}
})()
console.log(`[monero] waiting for RPC server to become available...`)
await this.waitForRpc()
console.log(`[monero] RPC server is ready`)
console.log(`[monero] connecting MoneroWalletRpc client...`)
this.wallet = await connectToWalletRpc(this.rpcUrl, this.rpcUser, this.rpcPassword) as MoneroWalletRpc
console.log(`[monero] MoneroWalletRpc connected successfully`)
}
private async rawRpc(method: string, params: Record<string, any> = {}): Promise<any> {
const conn = this.rpcConnection
const res = await conn.sendJsonRequest(method, params)
if (res?.error) throw new Error(res.error.message || JSON.stringify(res.error))
return res?.result
}
private async waitForRpc(timeoutMs = 30000): Promise<void> {
const start = Date.now()
let lastErr = ''
while (Date.now() - start < timeoutMs) {
try {
await Bun.connect({
hostname: '127.0.0.1',
port: this.rpcPort,
socket: { open(s) { s.end() }, close() {}, data() {}, error() {} },
})
console.log(`[monero] RPC ready after ${Date.now() - start}ms`)
return
} catch (e) {
lastErr = String(e)
}
await new Promise(r => setTimeout(r, 500))
}
throw new Error(`monero-wallet-rpc failed to start within ${timeoutMs}ms: ${lastErr}`)
}
async stop() {
console.log(`[monero] stopping wallet manager...`)
if (this.process) {
console.log(`[monero] killing wallet-rpc process (pid ${this.process.pid})`)
this.process.kill()
this.process = null
}
this.wallet = null
console.log(`[monero] wallet manager stopped`)
}
async createWallet(name: string, password: string) {
console.log(`[monero] creating wallet: ${name}`)
const result = await this.rawRpc('create_wallet', {
filename: name,
password,
language: 'English',
})
if (!this.wallet) throw new Error('Wallet RPC not started')
const address = await this.wallet.getAddress(0, 0)
console.log(`[monero] wallet created: ${name} -> ${address}`)
return { mnemonic: result.mnemonic, address }
}
async restoreWallet(name: string, password: string, mnemonic: string, restoreHeight?: number) {
console.log(`[monero] restoring wallet: ${name} (height: ${restoreHeight ?? 0})`)
await this.rawRpc('restore_deterministic_wallet', {
filename: name,
password,
seed: mnemonic,
restore_height: restoreHeight ?? 0,
language: 'English',
})
console.log(`[monero] wallet restored: ${name}`)
}
async openWallet(name: string, password: string) {
console.log(`[monero] opening wallet: ${name}`)
if (!this.wallet) throw new Error('Wallet RPC not started')
await this.wallet.openWallet(name, password)
console.log(`[monero] wallet opened: ${name}`)
}
async closeWallet() {
console.log(`[monero] closing current wallet`)
if (!this.wallet) throw new Error('Wallet RPC not started')
await this.wallet.close()
console.log(`[monero] wallet closed`)
}
async getBalance(): Promise<{ balance: bigint; unlocked: bigint }> {
if (!this.wallet) throw new Error('Wallet RPC not started')
const bal = await this.wallet.getBalance() as bigint
const unlocked = await this.wallet.getUnlockedBalance() as bigint
console.log(`[monero] balance: ${bal} (unlocked: ${unlocked})`)
return { balance: bal, unlocked }
}
async getAddress(accountIdx = 0, subIdx = 0): Promise<string> {
if (!this.wallet) throw new Error('Wallet RPC not started')
const address = await this.wallet.getAddress(accountIdx, subIdx) as string
console.log(`[monero] address: ${address}`)
return address
}
async getAccounts(): Promise<any[]> {
if (!this.wallet) throw new Error('Wallet RPC not started')
const accounts = await this.wallet.getAccounts(true) as any[]
console.log(`[monero] accounts: ${accounts?.length ?? 0} returned`)
return accounts?.map(a => a.toJson()) ?? []
}
async getTransactions(): Promise<any[]> {
if (!this.wallet) throw new Error('Wallet RPC not started')
const txs = await this.wallet.getTxs() as any[]
console.log(`[monero] transactions: ${txs?.length ?? 0} returned`)
return txs ?? []
}
async getHeight(): Promise<number> {
if (!this.wallet) throw new Error('Wallet RPC not started')
const height = await this.wallet.getHeight() as number
console.log(`[monero] wallet height: ${height}`)
return height
}
async getDaemonHeight(): Promise<number> {
if (!this.wallet) throw new Error('Wallet RPC not started')
try {
const daemon = await connectToDaemonRpc(`http://${this.daemonAddress}`)
const height = await daemon.getHeight() as number
console.log(`[monero] daemon height: ${height}`)
return height
} catch (e) {
console.log(`[monero] failed to get daemon height:`, e)
return 0
}
}
async isConnected(): Promise<boolean> {
if (!this.wallet) return false
try {
await this.wallet.getHeight()
return true
} catch (e) {
console.log(`[monero] connection check failed:`, e)
return false
}
}
}
+114
View File
@@ -1,5 +1,57 @@
import { Electroview } from 'electrobun/view'
export type MoneroBalance = {
balance: string
unlocked: string
address: string
height: number
daemonHeight: number
}
export type MoneroTxEntry = {
hash: string
amount: string
timestamp: string
direction: 'in' | 'out'
height: number
note?: string
}
export type MoneroBinaryStatus = {
installed: boolean
downloading: boolean
error?: string
}
export type MoneroWalletStatus = {
running: boolean
walletOpen: boolean
walletName?: string
connected: boolean
}
export type MoneroSubaddressEntry = {
accountIndex: number
index: number
address: string
label: string
balance: string
unlockedBalance: string
numUnspentOutputs: number
isUsed: boolean
numBlocksToUnlock: number
}
export type MoneroAccountEntry = {
index: number
primaryAddress: string
label: string
balance: string
unlockedBalance: string
tag?: string
subaddresses: MoneroSubaddressEntry[]
}
export type TxEntry = {
hash: string
timeStamp: string
@@ -82,6 +134,68 @@ type RPC = {
}
response: void
}
moneroBinaryStatus: {
params: {}
response: MoneroBinaryStatus
}
moneroDownloadBinary: {
params: {}
response: void
}
moneroStart: {
params: {
daemonAddress?: string
}
response: MoneroWalletStatus
}
moneroStop: {
params: {}
response: void
}
moneroCreateWallet: {
params: {
name: string
password: string
}
response: {
mnemonic: string
address: string
}
}
moneroRestoreWallet: {
params: {
name: string
password: string
mnemonic: string
restoreHeight?: number
}
response: {
address: string
}
}
moneroOpenWallet: {
params: {
name: string
password: string
}
response: void
}
moneroGetBalance: {
params: {}
response: MoneroBalance
}
moneroGetTransactions: {
params: {}
response: MoneroTxEntry[]
}
moneroWalletStatus: {
params: {}
response: MoneroWalletStatus
}
moneroGetAccounts: {
params: {}
response: MoneroAccountEntry[]
}
}
messages: {}
}
+198 -51
View File
@@ -1,5 +1,5 @@
import { JsonRpcProvider, HDNodeWallet, formatEther } from 'ethers'
import { electrobun, type TxEntry } from '$lib/electrobun'
import { electrobun, type TxEntry, type MoneroTxEntry, type MoneroAccountEntry } from '$lib/electrobun'
import { tryCatch } from '@koins/utils'
export const networks = [
@@ -27,6 +27,14 @@ export const networks = [
chainid: '1',
explorerUrl: 'https://etherscan.io/tx/',
},
{
id: 'monero',
name: 'Monero',
rpc: '',
symbol: 'XMR',
chainid: '',
explorerUrl: 'https://moneroblocks.info/tx/',
},
] as const
export type NetworkId = (typeof networks)[number]['id']
@@ -38,6 +46,13 @@ export type TokenBalance = {
logo?: string
}
export const atomicToXmr = (atomic: string): string => {
const n = BigInt(atomic)
const whole = n / 10_000_000_000_000n
const frac = (n % 10_000_000_000_000n).toString().padStart(12, '0').replace(/0+$/, '')
return frac ? `${whole}.${frac}` : `${whole}`
}
export const Wallet = () => {
let seed = $state('')
let address = $state('')
@@ -51,8 +66,24 @@ export const Wallet = () => {
let apiKey = $state('')
let transactions = $state<TxEntry[]>([])
let moneroRunning = $state(false)
let moneroWalletOpen = $state(false)
let moneroConnected = $state(false)
let moneroBalAtomic = $state('0')
let moneroUnlockedAtomic = $state('0')
let moneroAddress = $state('')
let moneroHeight = $state(0)
let moneroDaemonHeight = $state(0)
let moneroTxs = $state<MoneroTxEntry[]>([])
let moneroInstalled = $state(false)
let moneroDownloading = $state(false)
let moneroWalletName = $state('')
let moneroAccounts = $state<MoneroAccountEntry[]>([])
const net = () => networks.find((n) => n.id === network)!
const isMonero = () => network === 'monero'
const init = async () => {
if (!electrobun.rpc) return
const [raw, rawError] = await tryCatch(
@@ -95,7 +126,11 @@ export const Wallet = () => {
const switchNetwork = async (id: NetworkId) => {
network = id
if (seed) await refresh()
if (id === 'monero') {
await checkMoneroStatus()
} else if (seed) {
await refresh()
}
}
const saveVault = async (phrase: string) => {
@@ -163,6 +198,19 @@ export const Wallet = () => {
loading = false
}
const moneroFetchAccounts = async () => {
if (!electrobun.rpc) return false
const [accounts, error] = await tryCatch(
electrobun.rpc.request.moneroGetAccounts({}),
)
if (error) {
console.log('moneroFetchAccounts error:', error)
return false
}
moneroAccounts = accounts ?? []
return true
}
const saveApiKey = async (key: string) => {
await electrobun.rpc?.request.setSecret({
service: 'koins',
@@ -185,58 +233,148 @@ export const Wallet = () => {
transactions = txs ?? []
}
const checkMoneroStatus = async () => {
if (!electrobun.rpc) return
const [binStatus] = await tryCatch(
electrobun.rpc.request.moneroBinaryStatus({}),
)
if (binStatus) {
moneroInstalled = binStatus.installed
moneroDownloading = binStatus.downloading
}
const [status] = await tryCatch(
electrobun.rpc.request.moneroWalletStatus({}),
)
if (status) {
moneroRunning = status.running
moneroWalletOpen = status.walletOpen
moneroConnected = status.connected
}
}
const moneroDownload = async () => {
if (!electrobun.rpc) return
moneroDownloading = true
try {
await electrobun.rpc.request.moneroDownloadBinary({})
moneroInstalled = true
} finally {
moneroDownloading = false
}
}
const moneroStart = async () => {
if (!electrobun.rpc) return
const status = await electrobun.rpc.request.moneroStart({})
moneroRunning = status.running
moneroConnected = status.connected
}
const moneroStop = async () => {
if (!electrobun.rpc) return
await electrobun.rpc.request.moneroStop({})
moneroRunning = false
moneroWalletOpen = false
moneroConnected = false
moneroAddress = ''
moneroBalAtomic = '0'
moneroUnlockedAtomic = '0'
moneroTxs = []
moneroAccounts = []
}
const moneroCreateWallet = async (name: string, password: string) => {
if (!electrobun.rpc) throw new Error('RPC not available')
const result = await electrobun.rpc.request.moneroCreateWallet({ name, password })
moneroWalletName = name
moneroWalletOpen = true
moneroAddress = result.address
await moneroRefresh()
return result
}
const moneroRestoreWallet = async (name: string, password: string, mnemonic: string, restoreHeight?: number) => {
if (!electrobun.rpc) throw new Error('RPC not available')
const result = await electrobun.rpc.request.moneroRestoreWallet({
name, password, mnemonic, restoreHeight,
})
moneroWalletName = name
moneroWalletOpen = true
moneroAddress = result.address
await moneroRefresh()
}
const moneroOpenWallet = async (name: string, password: string) => {
if (!electrobun.rpc) return
await electrobun.rpc.request.moneroOpenWallet({ name, password })
moneroWalletName = name
moneroWalletOpen = true
await moneroRefresh()
}
const moneroRefresh = async () => {
if (!electrobun.rpc) return
loading = true
try {
const [bal] = await tryCatch(
electrobun.rpc.request.moneroGetBalance({}),
)
if (bal) {
moneroBalAtomic = bal.balance
moneroUnlockedAtomic = bal.unlocked
moneroAddress = bal.address
moneroHeight = bal.height
moneroDaemonHeight = bal.daemonHeight
}
const [txs] = await tryCatch(
electrobun.rpc.request.moneroGetTransactions({}),
)
moneroTxs = txs ?? []
await moneroFetchAccounts()
} catch (e) {
console.log('monero refresh error', e)
error = e instanceof Error ? e.message : 'Monero refresh failed'
} finally {
loading = false
}
}
return {
get isLocked() {
return vaultExists && !seed
},
get seed() {
return seed
},
get address() {
return address
},
get balance() {
return balance
},
get tokenBalances() {
return tokenBalances
},
get network() {
return network
},
get chainid() {
return net().chainid
},
get networkName() {
return net().name
},
get symbol() {
return net().symbol
},
get explorerUrl() {
return net().explorerUrl
},
get loading() {
return loading
},
get error() {
return error
},
get vaultExists() {
return vaultExists
},
get ready() {
return ready
},
get networks() {
return networks
},
get apiKey() {
return apiKey
},
get transactions() {
return transactions
return vaultExists && !seed && network !== 'monero'
},
get seed() { return seed },
get address() { return address },
get balance() { return balance },
get tokenBalances() { return tokenBalances },
get network() { return network },
get chainid() { return net().chainid },
get networkName() { return net().name },
get symbol() { return net().symbol },
get explorerUrl() { return net().explorerUrl },
get loading() { return loading },
get error() { return error },
get vaultExists() { return vaultExists },
get ready() { return ready },
get networks() { return networks },
get apiKey() { return apiKey },
get transactions() { return transactions },
isMonero,
get moneroRunning() { return moneroRunning },
get moneroWalletOpen() { return moneroWalletOpen },
get moneroConnected() { return moneroConnected },
get moneroBalAtomic() { return moneroBalAtomic },
get moneroUnlockedAtomic() { return moneroUnlockedAtomic },
get moneroBalance() { return atomicToXmr(moneroBalAtomic) },
get moneroUnlocked() { return atomicToXmr(moneroUnlockedAtomic) },
get moneroAddress() { return moneroAddress },
get moneroHeight() { return moneroHeight },
get moneroDaemonHeight() { return moneroDaemonHeight },
get moneroTxs() { return moneroTxs },
get moneroInstalled() { return moneroInstalled },
get moneroDownloading() { return moneroDownloading },
get moneroWalletName() { return moneroWalletName },
get moneroAccounts() { return moneroAccounts },
refresh,
init,
lock,
@@ -244,5 +382,14 @@ export const Wallet = () => {
switchNetwork,
saveApiKey,
fetchTxHistory,
checkMoneroStatus,
moneroDownload,
moneroStart,
moneroStop,
moneroCreateWallet,
moneroRestoreWallet,
moneroOpenWallet,
moneroRefresh,
moneroFetchAccounts,
}
}
+222 -18
View File
@@ -1,9 +1,10 @@
<script lang="ts">
import { electrobun, type TxEntry } from '$lib/electrobun.js'
import { Wallet } from '$lib/states/wallet.svelte.js'
import { electrobun, type TxEntry, type MoneroTxEntry } from '$lib/electrobun.js'
import { Wallet, atomicToXmr } from '$lib/states/wallet.svelte.js'
import { Button } from '$lib/components/ui/button/index.js'
import { navigate } from 'sv-router/generated'
import { Textarea } from '$lib/components/ui/textarea/index.js'
import { Input } from '$lib/components/ui/input/index.js'
import {
Card,
CardContent,
@@ -22,11 +23,12 @@
}
function nativeIcon(id: string) {
if (id === 'monero') return ''
return `${CDN}/${chainDir[id]}/info/logo.png`
}
function tokenIcon(id: string, address?: string) {
if (!address) return ''
if (!address || id === 'monero') return ''
return `${CDN}/${chainDir[id]}/assets/${address}/logo.png`
}
@@ -44,6 +46,11 @@
let inputSeed = $state('')
let apiKeyInput = $state('')
let moneroWalletName = $state('')
let moneroWalletPass = $state('')
let moneroMnemonic = $state('')
let moneroRestoreHeight = $state<number | undefined>(undefined)
$effect(() => {
w.init()
})
@@ -51,14 +58,12 @@
<div class="mx-auto mt-16 max-w-md">
{#if !w.ready}
<!-- loading -->
{:else if !w.vaultExists}
{:else if !w.vaultExists && !w.isMonero()}
<Card>
<CardHeader>
<CardTitle>Import Wallet</CardTitle>
<CardDescription>
Enter your seed phrase to save it securely in your system
keychain
Enter your seed phrase to save it securely in your system keychain
</CardDescription>
</CardHeader>
<CardContent>
@@ -77,7 +82,7 @@
{/if}
</CardContent>
</Card>
{:else if w.isLocked}
{:else if w.isLocked && !w.isMonero()}
<Card>
<CardContent>
<p class="text-muted-foreground text-xs">Unlocking...</p>
@@ -85,16 +90,7 @@
</Card>
{:else}
<div class="flex flex-col gap-4">
<Card>
<CardHeader>
<div class="flex items-center justify-between">
<div>
<CardTitle>Wallet</CardTitle>
<CardDescription>
Your wallet is unlocked
</CardDescription>
</div>
<div class="flex gap-1" role="group">
<div class="flex gap-1 justify-center" role="group">
{#each w.networks as net}
<button
class="cursor-pointer rounded-md px-2.5 py-1 text-xs font-medium transition-colors {w.network ===
@@ -106,6 +102,213 @@
</button>
{/each}
</div>
{#if w.isMonero()}
<!-- MONERO DASHBOARD -->
{#if w.moneroDownloading}
<Card>
<CardContent>
<p class="text-muted-foreground text-xs">Downloading Monero binary (70MB)...</p>
</CardContent>
</Card>
{:else if !w.moneroInstalled}
<Card>
<CardHeader>
<CardTitle>Monero Setup</CardTitle>
<CardDescription>Download monero-wallet-rpc to get started</CardDescription>
</CardHeader>
<CardContent>
<Button onclick={() => w.moneroDownload()}>Download (~70MB)</Button>
</CardContent>
</Card>
{:else if !w.moneroRunning}
<Card>
<CardHeader>
<CardTitle>Monero Wallet</CardTitle>
<CardDescription>Start the wallet RPC server to connect to the Monero network</CardDescription>
</CardHeader>
<CardContent>
<Button onclick={() => w.moneroStart()} disabled={w.loading}>
{w.loading ? 'Starting...' : 'Start'}
</Button>
</CardContent>
</Card>
{:else if !w.moneroWalletOpen}
<Card>
<CardHeader>
<CardTitle>Monero Wallet</CardTitle>
<CardDescription>Create a new wallet or restore an existing one</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-3">
<Input placeholder="Wallet name" bind:value={moneroWalletName} />
<Input type="password" placeholder="Password" bind:value={moneroWalletPass} />
<div class="flex gap-2">
<Button
onclick={async () => {
const result = await w.moneroCreateWallet(moneroWalletName, moneroWalletPass)
moneroMnemonic = result.mnemonic
}}
disabled={w.loading || !moneroWalletName || !moneroWalletPass}>
Create Wallet
</Button>
<Button
variant="outline"
onclick={() => w.moneroOpenWallet(moneroWalletName, moneroWalletPass)}
disabled={w.loading || !moneroWalletName || !moneroWalletPass}>
Open
</Button>
</div>
<hr class="border-muted" />
<p class="text-xs text-muted-foreground">Restore from seed</p>
<Textarea placeholder="Enter your Monero seed phrase (16 or 25 words)" bind:value={moneroMnemonic} />
<Input type="number" placeholder="Restore height (optional)" bind:value={moneroRestoreHeight} />
<Button
onclick={() => w.moneroRestoreWallet(moneroWalletName, moneroWalletPass, moneroMnemonic, moneroRestoreHeight)}
disabled={w.loading || !moneroWalletName || !moneroWalletPass || !moneroMnemonic.trim()}>
Restore Wallet
</Button>
</div>
</CardContent>
</Card>
{:else}
<!-- Monero wallet unlocked -->
<Card>
<CardHeader>
<div class="flex items-center justify-between">
<CardTitle>Monero Wallet</CardTitle>
<div class="flex items-center gap-1.5">
<div class="size-2 rounded-full {w.moneroConnected ? 'bg-green-500' : 'bg-red-500'}"></div>
<span class="text-xs text-muted-foreground">{w.moneroConnected ? 'Connected' : 'Disconnected'}</span>
</div>
</div>
<CardDescription>
{#if w.moneroHeight < w.moneroDaemonHeight}
Scanning: {w.moneroHeight.toLocaleString()} / {w.moneroDaemonHeight.toLocaleString()}
{:else}
Height: {w.moneroHeight.toLocaleString()}
{/if}
</CardDescription>
</CardHeader>
<CardContent>
<div class="mb-4 space-y-1">
<p class="font-medium text-xs">Address</p>
<p class="font-mono text-xs break-all">{w.moneroAddress}</p>
<p class="mt-2 font-medium text-xs">Balance</p>
<p class="font-mono text-lg">{w.moneroBalance} XMR</p>
{#if w.moneroUnlockedAtomic !== w.moneroBalAtomic}
<p class="text-xs text-muted-foreground">
Unlocked: {w.moneroUnlocked} XMR
</p>
{/if}
</div>
{#if moneroMnemonic}
<div class="mb-4 rounded-md bg-muted p-3">
<p class="text-xs font-medium mb-1">Seed phrase (save this!)</p>
<p class="font-mono text-xs break-all">{moneroMnemonic}</p>
</div>
{/if}
<div class="flex gap-2">
<Button onclick={() => { w.moneroRefresh(); w.moneroFetchAccounts(); }} disabled={w.loading}>
{w.loading ? 'Refreshing...' : 'Refresh'}
</Button>
<Button variant="outline" onclick={() => w.moneroStop()}>
Stop
</Button>
</div>
</CardContent>
</Card>
<!-- Monero transactions -->
<Card>
<CardHeader>
<CardTitle>Transactions</CardTitle>
</CardHeader>
<CardContent>
{#if w.moneroTxs.length === 0}
<p class="text-muted-foreground text-xs">No transactions found</p>
{:else}
<div class="max-h-96 space-y-1 overflow-y-auto">
{#each w.moneroTxs as tx}
<div class="flex items-center justify-between rounded-md bg-muted px-3 py-2 text-xs">
<div class="min-w-0 flex-1 space-y-0.5">
<p class="font-medium">
{tx.direction === 'in' ? 'Received' : 'Sent'}
</p>
<p class="font-mono text-muted-foreground">
{Number(tx.amount) > 0
? `${tx.direction === 'in' ? '+' : '-'}${(BigInt(tx.amount) / 10_000_000_000_000n).toString()} XMR`
: '0 XMR'}
</p>
{#if tx.height > 0}
<p class="text-muted-foreground">Block {tx.height}</p>
{/if}
<p class="text-muted-foreground/50 truncate font-mono">
{tx.hash.slice(0, 12)}...
</p>
</div>
</div>
{/each}
</div>
{/if}
</CardContent>
</Card>
<!-- Monero accounts & subaddresses -->
<Card>
<CardHeader>
<CardTitle>Accounts & Addresses</CardTitle>
<CardDescription>
{w.moneroAccounts.length} account{w.moneroAccounts.length !== 1 ? 's' : ''}
</CardDescription>
</CardHeader>
<CardContent>
{#each w.moneroAccounts as account}
<div class="mb-3 rounded-md bg-muted p-3">
<div class="mb-1 flex items-center justify-between">
<p class="text-xs font-medium">Account {account.index}</p>
<p class="text-xs text-muted-foreground">
{atomicToXmr(account.balance)} XMR
</p>
</div>
<p class="mb-2 font-mono text-xs text-muted-foreground break-all">
{account.primaryAddress}
</p>
{#if account.subaddresses.length > 1}
<div class="space-y-1 border-t border-border pt-2">
<p class="text-xs text-muted-foreground mb-1">Subaddresses</p>
{#each account.subaddresses as sub}
{#if sub.index > 0}
<div class="rounded-sm bg-background px-2 py-1.5 text-xs">
<div class="flex items-center justify-between">
<p class="font-medium">#{sub.index}{sub.label ? ` - ${sub.label}` : ''}</p>
<p class="text-muted-foreground">{atomicToXmr(sub.balance)} XMR</p>
</div>
<p class="font-mono text-muted-foreground/50 truncate">{sub.address}</p>
</div>
{/if}
{/each}
</div>
{/if}
</div>
{/each}
</CardContent>
</Card>
{/if}
{:else}
<!-- EVM DASHBOARD -->
<Card>
<CardHeader>
<div class="flex items-center justify-between">
<div>
<CardTitle>Wallet</CardTitle>
<CardDescription>
Your wallet is unlocked
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
@@ -274,6 +477,7 @@
{/if}
</CardContent>
</Card>
{/if}
</div>
{/if}
</div>
+69
View File
@@ -19,6 +19,7 @@
"electrobun": "^1.18.1",
"ethers": "^6.16.0",
"mode-watcher": "^1.1.0",
"monero-ts": "^0.11.8",
"sv-router": "latest",
"viem": "^2.48.11",
},
@@ -360,6 +361,8 @@
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -378,8 +381,14 @@
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
"async": ["async@2.6.4", "", { "dependencies": { "lodash": "^4.17.14" } }, "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
"axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
@@ -414,6 +423,8 @@
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
"concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
@@ -424,6 +435,8 @@
"cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="],
"crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
"d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="],
@@ -482,6 +495,8 @@
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
"deep-equal": ["deep-equal@2.2.3", "", { "dependencies": { "array-buffer-byte-length": "^1.0.0", "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "isarray": "^2.0.5", "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", "which-typed-array": "^1.1.13" } }, "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
@@ -496,6 +511,8 @@
"delaunator": ["delaunator@5.1.0", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
@@ -540,6 +557,8 @@
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@@ -562,10 +581,18 @@
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"follow-redirects": ["follow-redirects@1.16.0", "", { "peerDependencies": { "debug": "*" }, "optionalPeers": ["debug"] }, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"formsnap": ["formsnap@2.0.1", "", { "dependencies": { "svelte-toolbelt": "^0.5.0" }, "peerDependencies": { "svelte": "^5.0.0", "sveltekit-superforms": "^2.19.0" } }, "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -672,6 +699,8 @@
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
"koins": ["koins@workspace:apps/desktop"],
@@ -706,6 +735,8 @@
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
@@ -718,12 +749,18 @@
"memoize-weak": ["memoize-weak@1.0.2", "", {}, "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="],
"monero-ts": ["monero-ts@0.11.8", "", { "dependencies": { "@babel/runtime": "^7.23.2", "@types/node": "^20.6.0", "ajv": "^6.12.6", "async": "2.6.4", "axios": "^1.7.4", "crypto-js": "^4.0.0", "decimal.js": "^10.4.3", "net": "^1.0.2", "promise-throttle": "^1.1.2", "serialize-javascript": "^3.1.0", "text-encoding": "^0.7.0", "tls": "0.0.1", "uuid": "3.3.2", "web-worker": "1.3.0" } }, "sha512-+DFw/1ldAuVb7GDnYL9IbagDGJxY8mkHMuKsBqIBrf07Y/fTCZ8SZhJDg4FVFhYwMRdBad0V+nPhjoSeSyNa3Q=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
@@ -732,6 +769,8 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"net": ["net@1.0.2", "", {}, "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ=="],
"netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
@@ -778,16 +817,22 @@
"prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.2", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA=="],
"promise-throttle": ["promise-throttle@1.1.2", "", {}, "sha512-dij7vjyXNewuuN/gyr+TX2KRjw48mbV5FEtgyXaIoJjGYAKT0au23/voNvy9eS4UNJjx2KUdEcO5Yyfc1h7vWQ=="],
"property-expr": ["property-expr@2.0.6", "", {}, "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA=="],
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"quansync": ["quansync@1.0.0", "", {}, "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
"rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
@@ -814,12 +859,16 @@
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
"serialize-javascript": ["serialize-javascript@3.1.0", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg=="],
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
@@ -888,6 +937,8 @@
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"text-encoding": ["text-encoding@0.7.0", "", {}, "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA=="],
"three": ["three@0.165.0", "", {}, "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA=="],
"tiny-case": ["tiny-case@1.0.3", "", {}, "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q=="],
@@ -896,6 +947,8 @@
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"tls": ["tls@0.0.1", "", {}, "sha512-GzHpG+hwupY8VMR6rYsnAhTHqT/97zT45PG8WD5eTT1lq+dFE0nN+1PYpsoBcHJgSmTz5ceK2Cv88IkPmIPOtQ=="],
"toposort": ["toposort@2.0.2", "", {}, "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
@@ -928,6 +981,10 @@
"undici-types": ["undici-types@7.21.0", "", {}, "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"uuid": ["uuid@3.3.2", "", { "bin": { "uuid": "./bin/uuid" } }, "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="],
"valibot": ["valibot@1.4.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg=="],
"validator": ["validator@13.15.35", "", {}, "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw=="],
@@ -940,6 +997,8 @@
"vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="],
"web-worker": ["web-worker@1.3.0", "", {}, "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
@@ -1006,6 +1065,10 @@
"ast-types/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
"axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
"bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
@@ -1028,6 +1091,8 @@
"mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
"monero-ts/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="],
"ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="],
"ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="],
@@ -1068,6 +1133,8 @@
"@types/bun/bun-types/@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="],
"axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"d3-sankey/d3-array/internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
@@ -1078,6 +1145,8 @@
"mode-watcher/svelte-toolbelt/runed": ["runed@0.23.4", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA=="],
"monero-ts/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"paneforge/svelte-toolbelt/runed": ["runed@0.29.2", "", { "dependencies": { "esm-env": "^1.0.0" }, "peerDependencies": { "svelte": "^5.7.0" } }, "sha512-0cq6cA6sYGZwl/FvVqjx9YN+1xEBu9sDDyuWdDW1yWX7JF2wmvmVKfH+hVCZs+csW+P3ARH92MjI3H9QTagOQA=="],
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],