diff --git a/apps/desktop/src/bun/lib/monero/index.ts b/apps/desktop/src/bun/lib/monero/index.ts
index fe4ccae..b258625 100644
--- a/apps/desktop/src/bun/lib/monero/index.ts
+++ b/apps/desktop/src/bun/lib/monero/index.ts
@@ -17,6 +17,8 @@ export {
getDaemonHeight,
transfer,
sweepAll,
+ createAccount,
+ createSubaddress,
isWalletOpen,
isConnected,
listWallets,
diff --git a/apps/desktop/src/bun/lib/monero/wallet.ts b/apps/desktop/src/bun/lib/monero/wallet.ts
index f68be4f..29d8637 100644
--- a/apps/desktop/src/bun/lib/monero/wallet.ts
+++ b/apps/desktop/src/bun/lib/monero/wallet.ts
@@ -259,6 +259,32 @@ export async function getAddress(
return address
}
+export async function createAccount(
+ state: MoneroWalletState,
+ label?: string,
+): Promise<{ index: number; address: string }> {
+ if (!state.wallet) throw new Error('Wallet RPC not started')
+ const result = await rawRpc(state, 'create_account', {
+ label: label ?? '',
+ })
+ console.log(`[monero] created account ${result.account_index}: ${result.address}`)
+ return { index: result.account_index, address: result.address }
+}
+
+export async function createSubaddress(
+ state: MoneroWalletState,
+ accountIndex: number,
+ label?: string,
+): Promise<{ index: number; address: string }> {
+ if (!state.wallet) throw new Error('Wallet RPC not started')
+ const result = await rawRpc(state, 'create_address', {
+ account_index: accountIndex,
+ label: label ?? '',
+ })
+ console.log(`[monero] created subaddress ${result.address_index} for account ${accountIndex}: ${result.address}`)
+ return { index: result.address_index, address: result.address }
+}
+
export async function getAccounts(
state: MoneroWalletState,
): Promise {
diff --git a/apps/desktop/src/bun/rpc/monero/index.ts b/apps/desktop/src/bun/rpc/monero/index.ts
index fe43338..2ea61a9 100644
--- a/apps/desktop/src/bun/rpc/monero/index.ts
+++ b/apps/desktop/src/bun/rpc/monero/index.ts
@@ -18,6 +18,8 @@ import {
getFeeEstimate as walletGetFeeEstimate,
transfer as walletTransfer,
sweepAll as walletSweepAll,
+ createAccount as walletCreateAccount,
+ createSubaddress as walletCreateSubaddress,
isWalletOpen,
isConnected,
} from '../../lib/monero'
@@ -256,6 +258,30 @@ export function createMoneroHandlers(state: {
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)
diff --git a/apps/desktop/src/lib/rpc-schema.ts b/apps/desktop/src/lib/rpc-schema.ts
index 8b56554..889ea3d 100644
--- a/apps/desktop/src/lib/rpc-schema.ts
+++ b/apps/desktop/src/lib/rpc-schema.ts
@@ -180,6 +180,8 @@ export type MoneroRpcRequests = {
moneroListWallets: { params: {}; response: string[] }
moneroTransfer: { params: { address: string; amount: string; priority?: number; accountIndex?: number }; response: MoneroSendResult }
moneroSweepAll: { params: { address: string; priority?: number; accountIndex?: number }; response: MoneroSendResult }
+ moneroCreateAccount: { params: { label?: string }; response: { index: number; address: string } }
+ moneroCreateSubaddress: { params: { accountIndex: number; label?: string }; response: { index: number; address: string } }
moneroGetTransferDetails: { params: { txid: string }; response: MoneroTransferDetails | null }
moneroGetFeeEstimate: { params: {}; response: MoneroFeeEstimate | null }
}
diff --git a/apps/desktop/src/lib/states/monero-wallet.svelte.ts b/apps/desktop/src/lib/states/monero-wallet.svelte.ts
index 004c14f..9928ffb 100644
--- a/apps/desktop/src/lib/states/monero-wallet.svelte.ts
+++ b/apps/desktop/src/lib/states/monero-wallet.svelte.ts
@@ -308,6 +308,23 @@ export const MoneroWallet = () => {
return result
}
+ const createAccount = async (label?: string) => {
+ if (!electrobun.rpc) throw new Error('RPC not available')
+ const result = await electrobun.rpc.request.moneroCreateAccount({ label })
+ await refresh()
+ return result
+ }
+
+ const createSubaddress = async (accountIndex: number, label?: string) => {
+ if (!electrobun.rpc) throw new Error('RPC not available')
+ const result = await electrobun.rpc.request.moneroCreateSubaddress({
+ accountIndex,
+ label,
+ })
+ await refresh()
+ return result
+ }
+
return {
get accountType() {
return accountType
@@ -412,6 +429,8 @@ export const MoneroWallet = () => {
refresh,
send,
sendAll,
+ createAccount,
+ createSubaddress,
}
}
diff --git a/apps/desktop/src/routes/monero/index.svelte b/apps/desktop/src/routes/monero/index.svelte
index d0b0348..6b02b4e 100644
--- a/apps/desktop/src/routes/monero/index.svelte
+++ b/apps/desktop/src/routes/monero/index.svelte
@@ -7,6 +7,13 @@
import { Button } from '$lib/components/ui/button/index.js'
import { Textarea } from '$lib/components/ui/textarea/index.js'
import { Input } from '$lib/components/ui/input/index.js'
+ import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ } from '$lib/components/ui/select/index.js'
+ import * as Dialog from '$lib/components/ui/dialog/index.js'
import {
Card,
CardContent,
@@ -31,6 +38,13 @@
} catch {}
}
+ const chunkAddress = (addr: string) => {
+ const start = addr.slice(0, 6)
+ const mid = addr.slice(6, 12)
+ const end = addr.slice(-6)
+ return { start, mid, end }
+ }
+
let moneroWalletName = $state('')
let moneroWalletPass = $state('')
let moneroMnemonic = $state('')
@@ -48,6 +62,12 @@
estimatedFee: string
} | null>(null)
+ let accountDialogOpen = $state(false)
+ let accountLabel = $state('')
+ let subaddressDialogOpen = $state(false)
+ let subaddressAccountIndex = $state('')
+ let subaddressLabel = $state('')
+
$effect(() => {
if (w.walletOpen && !autoRefreshTimer) {
autoRefreshTimer = setInterval(() => w.refresh(), 30000)
@@ -91,7 +111,7 @@
{:else if !w.accountType}
Go to welcome page
- to get started
+ to get started
{:else if w.accountType === 'monero'}
{#if w.downloading}
@@ -283,6 +303,7 @@
{:else}
+ {@const a = chunkAddress(w.address)}
@@ -313,7 +334,14 @@
Address
- {w.address}
+
+ {a.start}
+
+ {a.mid}
+ ...
+
+ {a.end}
+
@@ -325,12 +353,19 @@
{/if}
{#if feeEstimate}
- {@const labels = ['Unimportant', 'Normal', 'Elevated', 'Priority']}
+ {@const labels = [
+ 'Unimportant',
+ 'Normal',
+ 'Elevated',
+ 'Priority',
+ ]}
{#each feeEstimate.fees as f, i}
{@const feeAtomic = (BigInt(f) * 2500n) / 1000n}
- {labels[i]}: ~{atomicToXmr(feeAtomic.toString())} XMR
+ {labels[i]}: ~{atomicToXmr(
+ feeAtomic.toString(),
+ )} XMR
{/each}
@@ -408,7 +443,12 @@
{#each w.txs as tx}