345 lines
9.1 KiB
TypeScript
345 lines
9.1 KiB
TypeScript
import {
|
|
isBinaryInstalled,
|
|
downloadBinary,
|
|
createMoneroWalletState,
|
|
start as walletStart,
|
|
stop as walletStop,
|
|
createWallet as walletCreate,
|
|
restoreWallet as walletRestore,
|
|
openWallet as walletOpen,
|
|
getBalance as walletGetBalance,
|
|
getAddress as walletGetAddress,
|
|
getTransactions as walletGetTransactions,
|
|
getAccounts as walletGetAccounts,
|
|
listWallets,
|
|
getTransferDetails as walletGetTransferDetails,
|
|
getHeight as walletGetHeight,
|
|
getDaemonHeight as walletGetDaemonHeight,
|
|
getFeeEstimate as walletGetFeeEstimate,
|
|
transfer as walletTransfer,
|
|
sweepAll as walletSweepAll,
|
|
createAccount as walletCreateAccount,
|
|
createSubaddress as walletCreateSubaddress,
|
|
isWalletOpen,
|
|
isConnected,
|
|
} from '../../lib/monero'
|
|
import type { MoneroWalletState } from '../../lib/monero'
|
|
import { log, logError } from '../../lib/logger'
|
|
|
|
export function createMoneroHandlers(state: {
|
|
manager: MoneroWalletState | null
|
|
downloading: boolean
|
|
}) {
|
|
return {
|
|
moneroBinaryStatus: async () => {
|
|
const status = {
|
|
installed: isBinaryInstalled(),
|
|
downloading: state.downloading,
|
|
}
|
|
console.log('[rpc] moneroBinaryStatus:', status)
|
|
return status
|
|
},
|
|
moneroDownloadBinary: async () => {
|
|
console.log('[rpc] moneroDownloadBinary: starting download...')
|
|
state.downloading = true
|
|
try {
|
|
await downloadBinary()
|
|
console.log('[rpc] moneroDownloadBinary: complete')
|
|
} catch (e) {
|
|
console.log('[rpc] moneroDownloadBinary failed:', e)
|
|
throw e
|
|
} finally {
|
|
state.downloading = false
|
|
}
|
|
},
|
|
moneroStart: async ({
|
|
daemonAddress,
|
|
}: {
|
|
daemonAddress?: string
|
|
}) => {
|
|
console.log('[rpc] moneroStart:', { daemonAddress })
|
|
if (!state.manager) {
|
|
state.manager = createMoneroWalletState()
|
|
}
|
|
try {
|
|
await walletStart(state.manager, daemonAddress)
|
|
} catch (e) {
|
|
console.log('[rpc] moneroStart error:', e)
|
|
logError('[rpc] moneroStart error', e)
|
|
return { running: false, walletOpen: false, connected: false }
|
|
}
|
|
const connected = await isConnected(state.manager)
|
|
console.log('[rpc] moneroStart complete, connected:', connected)
|
|
log(`[rpc] moneroStart complete, connected: ${connected}`)
|
|
return { running: true, walletOpen: false, connected }
|
|
},
|
|
moneroStop: async () => {
|
|
console.log('[rpc] moneroStop')
|
|
if (state.manager) {
|
|
await walletStop(state.manager)
|
|
state.manager = null
|
|
}
|
|
},
|
|
moneroCreateWallet: async ({
|
|
name,
|
|
password,
|
|
}: {
|
|
name: string
|
|
password: string
|
|
}) => {
|
|
console.log('[rpc] moneroCreateWallet:', name)
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const result = await walletCreate(state.manager, name, password)
|
|
console.log('[rpc] moneroCreateWallet complete')
|
|
return result
|
|
},
|
|
moneroRestoreWallet: async ({
|
|
name,
|
|
password,
|
|
mnemonic,
|
|
restoreHeight,
|
|
}: {
|
|
name: string
|
|
password: string
|
|
mnemonic: string
|
|
restoreHeight?: number
|
|
}) => {
|
|
console.log('[rpc] moneroRestoreWallet:', name)
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
// Default to ~2 days of history instead of a full genesis scan
|
|
// (which can take hours against a remote daemon).
|
|
let height = restoreHeight
|
|
if (!height || height === 0) {
|
|
const tip = await walletGetDaemonHeight(state.manager)
|
|
if (tip > 1440) height = tip - 1440
|
|
}
|
|
await walletRestore(
|
|
state.manager,
|
|
name,
|
|
password,
|
|
mnemonic,
|
|
height,
|
|
)
|
|
const address = await walletGetAddress(state.manager)
|
|
console.log(
|
|
'[rpc] moneroRestoreWallet complete, address:',
|
|
address,
|
|
)
|
|
return { address }
|
|
},
|
|
moneroOpenWallet: async ({
|
|
name,
|
|
password,
|
|
}: {
|
|
name: string
|
|
password: string
|
|
}) => {
|
|
console.log('[rpc] moneroOpenWallet:', name)
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
try {
|
|
await walletOpen(state.manager, name, password)
|
|
log(`[rpc] moneroOpenWallet ok: ${name}`)
|
|
} catch (e) {
|
|
logError(`[rpc] moneroOpenWallet failed (${name})`, e)
|
|
throw e
|
|
}
|
|
},
|
|
moneroGetBalance: async () => {
|
|
console.log('[rpc] moneroGetBalance')
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const { balance, unlocked } = await walletGetBalance(
|
|
state.manager,
|
|
)
|
|
const address = await walletGetAddress(state.manager)
|
|
const height = await walletGetHeight(state.manager)
|
|
const daemonHeight = await walletGetDaemonHeight(state.manager)
|
|
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 ({
|
|
accountIndex,
|
|
}: {
|
|
accountIndex?: number
|
|
}) => {
|
|
console.log('[rpc] moneroGetTransactions', { accountIndex })
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const txs = await walletGetTransactions(
|
|
state.manager,
|
|
accountIndex,
|
|
)
|
|
console.log(`[rpc] moneroGetTransactions: ${txs.length} txs`)
|
|
return txs
|
|
},
|
|
moneroWalletStatus: async () => {
|
|
if (!state.manager) {
|
|
console.log('[rpc] moneroWalletStatus: not running')
|
|
return { running: false, walletOpen: false, connected: false }
|
|
}
|
|
const connected = await isConnected(state.manager)
|
|
const walletOpen = await isWalletOpen(state.manager)
|
|
console.log(
|
|
'[rpc] moneroWalletStatus: running, walletOpen:',
|
|
walletOpen,
|
|
'connected:',
|
|
connected,
|
|
)
|
|
return { running: true, walletOpen, connected }
|
|
},
|
|
moneroGetAccounts: async () => {
|
|
console.log('[rpc] moneroGetAccounts')
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const accounts = await walletGetAccounts(state.manager)
|
|
console.log(
|
|
`[rpc] moneroGetAccounts: ${accounts.length} accounts`,
|
|
)
|
|
return accounts
|
|
},
|
|
moneroListWallets: async () => {
|
|
console.log('[rpc] moneroListWallets')
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const wallets = listWallets()
|
|
console.log(
|
|
`[rpc] moneroListWallets: ${wallets.join(', ') || 'none'}`,
|
|
)
|
|
return wallets
|
|
},
|
|
moneroTransfer: async ({
|
|
address,
|
|
amount,
|
|
priority,
|
|
accountIndex,
|
|
}: {
|
|
address: string
|
|
amount: string
|
|
priority?: number
|
|
accountIndex?: number
|
|
}) => {
|
|
console.log('[rpc] moneroTransfer:', {
|
|
address,
|
|
amount,
|
|
priority,
|
|
})
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const result = await walletTransfer(
|
|
state.manager,
|
|
address,
|
|
BigInt(amount),
|
|
priority ?? 0,
|
|
accountIndex ?? 0,
|
|
)
|
|
console.log('[rpc] moneroTransfer complete:', result.txHash)
|
|
return result
|
|
},
|
|
moneroSweepAll: async ({
|
|
address,
|
|
priority,
|
|
accountIndex,
|
|
}: {
|
|
address: string
|
|
priority?: number
|
|
accountIndex?: number
|
|
}) => {
|
|
console.log('[rpc] moneroSweepAll:', {
|
|
address,
|
|
priority,
|
|
})
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const result = await walletSweepAll(
|
|
state.manager,
|
|
address,
|
|
priority ?? 0,
|
|
accountIndex ?? 0,
|
|
)
|
|
console.log('[rpc] moneroSweepAll complete:', result.txHash)
|
|
return result
|
|
},
|
|
moneroCreateAccount: async ({ label }: { label?: string }) => {
|
|
console.log('[rpc] moneroCreateAccount:', { label })
|
|
if (!state.manager) throw new Error('Monero wallet RPC not started')
|
|
const result = await walletCreateAccount(state.manager, label)
|
|
console.log('[rpc] moneroCreateAccount complete:', result)
|
|
return result
|
|
},
|
|
moneroCreateSubaddress: async ({
|
|
accountIndex,
|
|
label,
|
|
}: {
|
|
accountIndex: number
|
|
label?: string
|
|
}) => {
|
|
console.log('[rpc] moneroCreateSubaddress:', { accountIndex, label })
|
|
if (!state.manager) throw new Error('Monero wallet RPC not started')
|
|
const result = await walletCreateSubaddress(
|
|
state.manager,
|
|
accountIndex,
|
|
label,
|
|
)
|
|
console.log('[rpc] moneroCreateSubaddress complete:', result)
|
|
return result
|
|
},
|
|
moneroGetTransferDetails: async ({ txid }: { txid: string }) => {
|
|
console.log('[rpc] moneroGetTransferDetails:', txid)
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
const details = await walletGetTransferDetails(
|
|
state.manager,
|
|
txid,
|
|
)
|
|
console.log(
|
|
'[rpc] moneroGetTransferDetails complete:',
|
|
details?.hash,
|
|
)
|
|
return details
|
|
},
|
|
moneroGetFeeEstimate: async () => {
|
|
console.log('[rpc] moneroGetFeeEstimate')
|
|
if (!state.manager)
|
|
throw new Error('Monero wallet RPC not started')
|
|
try {
|
|
const fee = await walletGetFeeEstimate(state.manager)
|
|
console.log('[monero][gas][fee]', fee)
|
|
return fee
|
|
} catch (error) {
|
|
console.log('[monero][gas][error]', error)
|
|
return null
|
|
}
|
|
},
|
|
fetchMoneroPrice: async () => {
|
|
try {
|
|
const res = await fetch(
|
|
'https://api.coingecko.com/api/v3/simple/price?ids=monero&vs_currencies=usd',
|
|
{ signal: AbortSignal.timeout(10000) },
|
|
)
|
|
if (!res.ok) return null
|
|
const data = await res.json()
|
|
if (!data?.monero?.usd) return null
|
|
return { usd: String(data.monero.usd) }
|
|
} catch (error) {
|
|
console.log('[monero][price] error:', error)
|
|
return null
|
|
}
|
|
},
|
|
}
|
|
}
|