diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index 75b4ae66..20dbc648 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -132,6 +132,11 @@ const SEED_WALLET = { channel_sats: 8_250_000, ecash_sats: 250_000, ecash_tokens: 12, + // Ark (barkd sidecar) demo balances — signet defaults + ark_sats: 75_000, + ark_pending_sats: 0, + ark_onchain_sats: 20_000, + ark_config: { network: 'signet', ark_server: 'https://ark.signet.2nd.dev', esplora: 'https://esplora.signet.2nd.dev' }, block_height: 892451, transactions: [ { tx_hash: 'ab12cd34ef5678901234567890abcdef12345678', amount_sats: 2_000_000, direction: 'incoming', num_confirmations: 142, block_height: 892310, time_stamp: Math.floor(Date.now()/1000) - 86400, label: 'Channel funding', total_fees: 0, dest_addresses: [] }, @@ -2209,6 +2214,63 @@ app.post('/rpc/v1', (req, res) => { const total = (mockState.federations || []).reduce((s, f) => s + (f.balance_sats || 0), 0) return res.json({ result: { balance_sats: total } }) } + case 'wallet.ark-status': { + return res.json({ + result: { + available: true, + wallet_ready: true, + wallet: { network: walletState.ark_config.network }, + ark_info: { server: walletState.ark_config.ark_server }, + config: walletState.ark_config, + }, + }) + } + case 'wallet.ark-balance': { + return res.json({ + result: { + balance_sats: walletState.ark_sats, + spendable_sats: walletState.ark_sats, + pending_sats: walletState.ark_pending_sats, + onchain_sats: walletState.ark_onchain_sats, + }, + }) + } + case 'wallet.ark-address': { + const onchain = params?.onchain === true + const address = onchain ? 'tb1q' + randomHex(16) : 'tark1' + randomHex(28) + return res.json({ result: { address, onchain } }) + } + case 'wallet.ark-board': { + const amt = params?.amount_sats ?? walletState.ark_onchain_sats + walletState.ark_onchain_sats = Math.max(0, walletState.ark_onchain_sats - amt) + walletState.ark_sats += amt + return res.json({ result: { boarded_sats: amt } }) + } + case 'wallet.ark-offboard': { + const amt = walletState.ark_sats + walletState.ark_sats = 0 + walletState.ark_onchain_sats += amt + return res.json({ result: { offboarded_sats: amt } }) + } + case 'wallet.ark-send': { + const amt = params?.amount_sats || 1000 + walletState.ark_sats = Math.max(0, walletState.ark_sats - amt) + return res.json({ result: { sent: true, movement: { id: 1, effective_balance_sat: -amt } } }) + } + case 'wallet.ark-invoice': { + return res.json({ result: { invoice: 'lntbs' + randomHex(64) } }) + } + case 'wallet.ark-history': { + return res.json({ result: { transactions: [] } }) + } + case 'wallet.ark-configure': { + const cfg = { ...walletState.ark_config } + for (const key of ['network', 'ark_server', 'esplora']) { + if (typeof params?.[key] === 'string' && params[key].trim()) cfg[key] = params[key].trim() + } + walletState.ark_config = cfg + return res.json({ result: { config: cfg } }) + } case 'content.request-onchain': { const price = params?.price_sats || 500 return res.json({ @@ -3225,6 +3287,8 @@ app.post('/rpc/v1', (req, res) => { { type: 'receive', amount_sats: 50000, timestamp: new Date(Date.now() - 86400000).toISOString(), note: 'Minted from Lightning' }, { type: 'send', amount_sats: 5000, timestamp: new Date(Date.now() - 43200000).toISOString(), note: 'Sent ecash token' }, { type: 'receive', amount_sats: 10000, timestamp: new Date(Date.now() - 3600000).toISOString(), note: 'Redeemed token' }, + // Ark movement in the unified history shape (see wallet.ark-history / load_ark_txs) + { id: 'ark-1', tx_type: 'receive', type: 'receive', amount_sats: 75000, timestamp: new Date(Date.now() - 7200000).toISOString(), description: 'Received via Ark', note: 'Received via Ark', mint_url: '', peer: '', kind: 'ark' }, ], }, }) diff --git a/neode-ui/public/assets/img/app-icons/bark.png b/neode-ui/public/assets/img/app-icons/bark.png new file mode 100644 index 00000000..e9be9a0b Binary files /dev/null and b/neode-ui/public/assets/img/app-icons/bark.png differ diff --git a/neode-ui/public/catalog.json b/neode-ui/public/catalog.json index a3c3aaa3..ca1b6a22 100644 --- a/neode-ui/public/catalog.json +++ b/neode-ui/public/catalog.json @@ -298,6 +298,25 @@ ] } }, + { + "id": "barkd", + "title": "Ark Wallet", + "version": "0.3.0", + "description": "Ark protocol wallet daemon (barkd). Lets the node hold self-custodial off-chain bitcoin via an Ark server; the wallet talks to it over a local REST API. Signet by default while Ark matures.", + "icon": "/assets/img/app-icons/bark.png", + "author": "Second", + "category": "money", + "dockerImage": "146.59.87.168:3000/lfg2025/barkd:0.3.0", + "repoUrl": "https://gitlab.com/ark-bitcoin/bark", + "containerConfig": { + "ports": [ + "3535:3535" + ], + "volumes": [ + "/var/lib/archipelago/barkd:/data" + ] + } + }, { "id": "jellyfin", "title": "Jellyfin", diff --git a/neode-ui/scripts/download-app-icons.js b/neode-ui/scripts/download-app-icons.js index 6fa5ca2f..7cbb9d12 100755 --- a/neode-ui/scripts/download-app-icons.js +++ b/neode-ui/scripts/download-app-icons.js @@ -45,6 +45,10 @@ const customIconUrls = { 'fedimint': [ 'https://raw.githubusercontent.com/fedibtc/fedimint-ui/master/apps/router/public/favicon.svg', ], + // Official bark project avatar (Ark protocol wallet daemon) + 'bark': [ + 'https://gitlab.com/uploads/-/system/project/avatar/75519706/bark-smiling-square-white-2.jpg', + ], } const iconDir = path.join(__dirname, '../public/assets/img/app-icons') diff --git a/neode-ui/src/components/TransactionsModal.vue b/neode-ui/src/components/TransactionsModal.vue index 8d174364..a2a831af 100644 --- a/neode-ui/src/components/TransactionsModal.vue +++ b/neode-ui/src/components/TransactionsModal.vue @@ -52,7 +52,7 @@ {{ kindLabel(tx) }} @@ -90,7 +90,7 @@ interface WalletTransaction { label: string block_height: number // Which rail the transaction happened on; absent = onchain (older backends) - kind?: 'onchain' | 'lightning' | 'cashu' | 'fedimint' + kind?: 'onchain' | 'lightning' | 'cashu' | 'fedimint' | 'ark' } defineProps<{ @@ -114,6 +114,7 @@ function kindLabel(tx: WalletTransaction): string { if (tx.kind === 'lightning') return '⚡ Lightning' if (tx.kind === 'cashu') return 'Cashu' if (tx.kind === 'fedimint') return 'Fedimint' + if (tx.kind === 'ark') return 'Ark' return '' } diff --git a/neode-ui/src/components/WalletSettingsModal.vue b/neode-ui/src/components/WalletSettingsModal.vue index 38f5a666..57d403fe 100644 --- a/neode-ui/src/components/WalletSettingsModal.vue +++ b/neode-ui/src/components/WalletSettingsModal.vue @@ -148,11 +148,107 @@ Joining federations lands with the Fedimint client backend.

+ + +
+
+

+ Ark holds self-custodial off-chain bitcoin via an Ark server. Funds stay recoverable on-chain even if the server disappears. +

+ Not installed + {{ arkStatus.config.network }} +
+ +
Checking Ark wallet…
+ + +
diff --git a/neode-ui/src/views/Home.vue b/neode-ui/src/views/Home.vue index fcb963c3..e06072f6 100644 --- a/neode-ui/src/views/Home.vue +++ b/neode-ui/src/views/Home.vue @@ -133,6 +133,7 @@ :wallet-lightning="walletLightning" :wallet-ecash="walletEcash" :wallet-fedimint="walletFedimint" + :wallet-ark="walletArk" :wallet-transactions="walletTransactions" :is-dev="isDev" @show-send="showSendModal = true" @@ -515,6 +516,7 @@ const showSendModal = ref(false); const showReceiveModal = ref(false); const sho async function devFaucet() { try { await rpcClient.call({ method: 'dev.faucet', params: { amount_sats: 1_000_000 } }); await loadWeb5Status() } catch { /* ignore */ } } const walletConnected = ref(false); const walletOnchain = ref(0); const walletLightning = ref(0); const walletEcash = ref(0); const walletFedimint = ref(0) +const walletArk = ref(0) const walletTransactions = ref([]) // Overlay the explorer above the current page — never navigate away. @@ -532,7 +534,7 @@ interface EcashTransaction { description: string mint_url: string peer: string - kind: 'cashu' | 'fedimint' + kind: 'cashu' | 'fedimint' | 'ark' } function ecashToWalletTransaction(tx: EcashTransaction): WalletTransaction { @@ -557,6 +559,7 @@ async function loadWeb5Status() { try { const res = await rpcClient.call<{ balance_sats: number; channel_balance_sats: number }>({ method: 'lnd.getinfo', timeout: 5000 }); walletOnchain.value = res.balance_sats || 0; walletLightning.value = res.channel_balance_sats || 0; walletConnected.value = true } catch { walletConnected.value = false } try { const res = await rpcClient.call<{ balance_sats: number }>({ method: 'wallet.ecash-balance', timeout: 5000 }); walletEcash.value = res.balance_sats ?? 0 } catch { /* keep last-known balance */ } try { const res = await rpcClient.call<{ balance_sats: number }>({ method: 'wallet.fedimint-balance', timeout: 5000 }); walletFedimint.value = res.balance_sats ?? 0 } catch { /* keep last-known balance */ } + try { const res = await rpcClient.call<{ spendable_sats: number }>({ method: 'wallet.ark-balance', timeout: 5000 }); walletArk.value = res.spendable_sats ?? 0 } catch { /* keep last-known balance */ } // Merge LND transactions with ecash/Fedimint history (wallet.ecash-history // already unifies both) — previously only LND transactions were fetched // here, so any Cashu or Fedimint receive (e.g. a TollGate payment) never diff --git a/neode-ui/src/views/home/HomeWalletCard.vue b/neode-ui/src/views/home/HomeWalletCard.vue index 94c5fcbe..2c8d174b 100644 --- a/neode-ui/src/views/home/HomeWalletCard.vue +++ b/neode-ui/src/views/home/HomeWalletCard.vue @@ -136,6 +136,14 @@ {{ walletFedimint.toLocaleString() }} sats + +
+
+ + Ark +
+ {{ (walletArk ?? 0).toLocaleString() }} sats +