@meddleware/dao-ui 0.1.2 β†’ 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/dao-ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Meddleware DAO console β€” treasury overview, proposals, and governance on Sui.",
5
5
  "author": "Meddleware <dev@meddleware.co.uk>",
6
6
  "license": "0BSD",
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@meddleware/design-tokens": "^0.1.2",
43
- "@meddleware/ui": "^0.1.10",
43
+ "@meddleware/ui": "^0.1.11",
44
44
  "@meddleware/wallet-adapter": "^0.0.5",
45
45
  "@mysten/sui": "^2.30.0",
46
46
  "vue": "^3.5.40"
package/src/DaoView.vue CHANGED
@@ -6,7 +6,7 @@
6
6
  // qt.css is imported here (not just in main.ts) so the styles are included when
7
7
  // DaoView is consumed as a library by the dashboard or any other host.
8
8
  import './styles/qt.css'
9
- import { ref, shallowRef, computed } from 'vue'
9
+ import { ref, computed } from 'vue'
10
10
  import TabBar from './components/TabBar.vue'
11
11
  import StatusBar from './components/StatusBar.vue'
12
12
  import OverviewTab from './tabs/OverviewTab.vue'
@@ -47,10 +47,6 @@ const { epoch } = useEpoch()
47
47
  <div class="dao-view">
48
48
  <div class="dao-toolbar">
49
49
  <button class="dao-toolbar__btn" @click="activeTab = 'overview'">πŸ› Meddleware DAO</button>
50
- <span class="dao-toolbar__sep" />
51
- <span class="dao-muted" style="font-size: 0.75rem; padding: 0 4px">
52
- access_gate Β· testnet
53
- </span>
54
50
  </div>
55
51
 
56
52
  <TabBar :tabs="TABS" v-model="activeTab" />
@@ -5,7 +5,7 @@ import { PACKAGE_ID } from '../config.js'
5
5
  export interface DaoEvent {
6
6
  type: 'AccessMinted' | 'AccessConsumed' | 'GateCreated' | 'AccessBurned'
7
7
  txDigest: string
8
- timestampMs: number
8
+ checkpoint: string | null
9
9
  address?: string
10
10
  }
11
11
 
@@ -47,14 +47,20 @@ export function useDaoEvents(limit = 20) {
47
47
  all.push({
48
48
  type: label as DaoEvent['type'],
49
49
  txDigest: e.transactionDigest,
50
- timestampMs: 0,
50
+ checkpoint: e.checkpoint ?? null,
51
51
  address: String(f.recipient ?? f.creator ?? f.sender ?? ''),
52
52
  })
53
53
  }
54
54
  }
55
55
 
56
- // Events come back in descending order per-type; stable sort across types by txDigest.
57
- // timestampMs is not available from gRPC EventEntry; order is already newest-first per batch.
56
+ // Sort globally by checkpoint descending so the merged list is newest-first across all types.
57
+ all.sort((a, b) => {
58
+ const ca = BigInt(a.checkpoint ?? '0')
59
+ const cb = BigInt(b.checkpoint ?? '0')
60
+ if (cb > ca) return 1
61
+ if (cb < ca) return -1
62
+ return 0
63
+ })
58
64
  events.value = all.slice(0, limit)
59
65
  lastRefresh.value = new Date()
60
66
  } catch (e) {
@@ -9,7 +9,7 @@ export interface Gate {
9
9
  paused: boolean
10
10
  frozen: boolean
11
11
  txDigest: string
12
- timestampMs: number
12
+ checkpoint: string | null
13
13
  }
14
14
 
15
15
  export function useGates() {
@@ -37,7 +37,7 @@ export function useGates() {
37
37
  paused: false,
38
38
  frozen: false,
39
39
  txDigest: e.transactionDigest,
40
- timestampMs: 0,
40
+ checkpoint: e.checkpoint ?? null,
41
41
  }
42
42
  })
43
43
  } catch (e) {
package/src/styles/qt.css CHANGED
@@ -131,6 +131,9 @@
131
131
  border-top: 1px solid var(--border);
132
132
  flex-shrink: 0;
133
133
  font-family: var(--font-mono);
134
+ position: sticky;
135
+ bottom: 0;
136
+ z-index: 1;
134
137
  }
135
138
 
136
139
  .dao-statusbar__dot {
@@ -319,6 +322,36 @@
319
322
  font-size: 0.72rem;
320
323
  }
321
324
 
325
+ /* ── Info tooltip marker ─────────────────────────────────────────────────── */
326
+
327
+ .dao-info {
328
+ display: inline-flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ width: 13px;
332
+ height: 13px;
333
+ border-radius: 50%;
334
+ border: 1px solid var(--muted);
335
+ color: var(--muted);
336
+ font-size: 0.58rem;
337
+ font-weight: 700;
338
+ font-style: normal;
339
+ cursor: help;
340
+ vertical-align: middle;
341
+ margin-left: 3px;
342
+ text-decoration: none;
343
+ }
344
+
345
+ /* ── Pagination ──────────────────────────────────────────────────────────── */
346
+
347
+ .dao-pagination {
348
+ display: flex;
349
+ align-items: center;
350
+ gap: 10px;
351
+ justify-content: center;
352
+ margin-top: 10px;
353
+ }
354
+
322
355
  /* ── Misc ────────────────────────────────────────────────────────────────── */
323
356
 
324
357
  .dao-mono {
@@ -1,19 +1,52 @@
1
1
  <script setup lang="ts">
2
- import { computed } from 'vue'
2
+ import { ref, watch, computed } from 'vue'
3
3
  import Panel from '../components/Panel.vue'
4
4
  import { usePlatformConfig } from '../composables/usePlatformConfig.js'
5
+ import { useWallet, getSuiClient } from '../wallet.js'
6
+ import { PACKAGE_ID } from '../config.js'
7
+ import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
8
+ import { NETWORK } from '../config.js'
5
9
 
6
10
  const { config } = usePlatformConfig()
11
+ const { account } = useWallet()
7
12
 
8
13
  const commissionPct = computed(() =>
9
14
  config.value ? (config.value.commissionBps / 100).toFixed(2) + '%' : 'β€”',
10
15
  )
16
+
17
+ type CapState = 'idle' | 'checking' | 'found' | 'not-found' | 'error'
18
+ const capState = ref<CapState>('idle')
19
+
20
+ async function checkCaps(address: string) {
21
+ if (!PACKAGE_ID) { capState.value = 'not-found'; return }
22
+ capState.value = 'checking'
23
+ try {
24
+ const client = getSuiClient()
25
+ const res = await client.listOwnedObjects({
26
+ owner: address,
27
+ type: `${PACKAGE_ID}::access_gate::PlatformAdminCap`,
28
+ limit: 1,
29
+ })
30
+ capState.value = res.objects.length > 0 ? 'found' : 'not-found'
31
+ } catch {
32
+ capState.value = 'error'
33
+ }
34
+ }
35
+
36
+ watch(
37
+ () => account.value?.address,
38
+ (addr) => {
39
+ if (addr) void checkCaps(addr)
40
+ else capState.value = 'idle'
41
+ },
42
+ { immediate: true },
43
+ )
11
44
  </script>
12
45
 
13
46
  <template>
14
47
  <div style="display: flex; flex-direction: column; gap: 10px">
15
- <Panel title="On-Chain Parameters (access_gate)">
16
- <div class="dao-stat-grid" style="max-width: 420px">
48
+ <Panel title="Platform Parameters">
49
+ <div class="dao-stat-grid" style="max-width: 480px">
17
50
  <span class="dao-stat-grid__label">Commission rate</span>
18
51
  <span class="dao-stat-grid__value dao-mono">{{ commissionPct }}</span>
19
52
 
@@ -21,34 +54,61 @@ const commissionPct = computed(() =>
21
54
  <span class="dao-stat-grid__value dao-mono">10.00% (1000 bps β€” on-chain hard cap)</span>
22
55
 
23
56
  <span class="dao-stat-grid__label">Treasury address</span>
24
- <span
25
- class="dao-stat-grid__value dao-mono"
26
- style="font-size: 0.72rem; text-align: left; word-break: break-all"
27
- >
28
- {{ config?.treasury ?? 'β€”' }}
57
+ <span class="dao-stat-grid__value" style="text-align: left">
58
+ <CopyableAddress v-if="config?.treasury" :address="config.treasury">
59
+ <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
60
+ </CopyableAddress>
61
+ <span v-else class="dao-mono" style="font-size: 0.7rem">β€”</span>
29
62
  </span>
30
63
  </div>
31
64
 
32
65
  <p class="dao-muted" style="margin: 10px 0 0; font-size: 0.72rem">
33
66
  Parameters are governed by <code>PlatformAdminCap</code> on-chain.
34
- Commission is split on every NFT purchase: <code>commission_bps / 10000 Γ— price</code>
35
- routes to treasury; the remainder goes to the gate operator.
36
- </p>
37
- </Panel>
38
-
39
- <Panel title="Vault DAO Parameters">
40
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
41
- DAO-governed vault parameters (skim fee, buyback ratio, allocation caps, fee recipients)
42
- will appear here when <code>vault_config</code> and <code>vault_dao</code> are deployed.
67
+ Commission is charged on every NFT access purchase:
68
+ <code>commission_bps / 10000 Γ— price</code> routes to treasury;
69
+ the remainder goes to the gate operator.
43
70
  </p>
44
71
  </Panel>
45
72
 
46
73
  <Panel title="Admin Actions">
47
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
48
- Privileged actions (update commission, change treasury, pause gates) require the
49
- <code>PlatformAdminCap</code> or <code>DaoAdminCap</code>.
50
- Connect a wallet holding the relevant capability to enable these controls.
51
- </p>
74
+ <!-- Not connected -->
75
+ <template v-if="!account">
76
+ <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
77
+ Connect a wallet via the header to manage platform settings.
78
+ </p>
79
+ </template>
80
+
81
+ <!-- Connected, checking -->
82
+ <template v-else-if="capState === 'checking'">
83
+ <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
84
+ Checking capabilities for {{ account.address.slice(0, 10) }}…
85
+ </p>
86
+ </template>
87
+
88
+ <!-- Connected, has PlatformAdminCap -->
89
+ <template v-else-if="capState === 'found'">
90
+ <p class="dao-muted" style="margin: 0 0 8px; font-size: 0.78rem">
91
+ <span style="color: var(--ok)">●</span>
92
+ <code>PlatformAdminCap</code> detected. Admin controls will appear here when implemented.
93
+ </p>
94
+ </template>
95
+
96
+ <!-- Connected, no cap -->
97
+ <template v-else-if="capState === 'not-found'">
98
+ <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
99
+ <span>●</span>
100
+ Connected: {{ account.address.slice(0, 10) }}…{{ account.address.slice(-6) }}<br>
101
+ This wallet does not hold <code>PlatformAdminCap</code> or <code>DaoAdminCap</code>.
102
+ Privileged actions are not available.
103
+ </p>
104
+ </template>
105
+
106
+ <!-- Error -->
107
+ <template v-else-if="capState === 'error'">
108
+ <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
109
+ Unable to verify capabilities. Check your connection and try again.
110
+ </p>
111
+ </template>
52
112
  </Panel>
53
113
  </div>
54
114
  </template>
@@ -1,9 +1,23 @@
1
1
  <script setup lang="ts">
2
+ import { ref, computed } from 'vue'
2
3
  import DataTable from '../components/DataTable.vue'
3
4
  import { useDaoEvents } from '../composables/useDaoEvents.js'
5
+ import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
6
+ import { NETWORK } from '../config.js'
4
7
  import type { DaoEvent } from '../composables/useDaoEvents.js'
5
8
 
6
- const { events, loading, error, reload } = useDaoEvents(50)
9
+ const { events, loading, error, reload } = useDaoEvents(200)
10
+
11
+ const PAGE_SIZE = 20
12
+ const page = ref(1)
13
+
14
+ const totalPages = computed(() => Math.max(1, Math.ceil(events.value.length / PAGE_SIZE)))
15
+ const pageEvents = computed(() =>
16
+ events.value.slice((page.value - 1) * PAGE_SIZE, page.value * PAGE_SIZE),
17
+ )
18
+
19
+ function prevPage() { if (page.value > 1) page.value-- }
20
+ function nextPage() { if (page.value < totalPages.value) page.value++ }
7
21
 
8
22
  const eventLabel: Record<DaoEvent['type'], string> = {
9
23
  AccessMinted: 'Access Sold',
@@ -12,19 +26,6 @@ const eventLabel: Record<DaoEvent['type'], string> = {
12
26
  AccessBurned: 'Access Burned',
13
27
  }
14
28
 
15
- function shortTs(ms: number): string {
16
- if (!ms) return 'β€”'
17
- return new Date(ms).toISOString().replace('T', ' ').slice(0, 19) + ' UTC'
18
- }
19
-
20
- function shortAddr(addr: string | undefined): string {
21
- if (!addr || addr === 'undefined') return 'β€”'
22
- return addr.slice(0, 10) + '…' + addr.slice(-6)
23
- }
24
-
25
- function explorerUrl(digest: string): string {
26
- return `https://suiscan.xyz/testnet/tx/${digest}`
27
- }
28
29
  </script>
29
30
 
30
31
  <template>
@@ -38,14 +39,23 @@ function explorerUrl(digest: string): string {
38
39
 
39
40
  <p v-if="error" class="dao-muted">{{ error }}</p>
40
41
 
41
- <DataTable v-if="events.length || loading" :empty="'No events'">
42
+ <DataTable v-if="pageEvents.length || loading" :empty="'No events'">
42
43
  <template #head>
43
44
  <th>Type</th>
44
- <th>Address</th>
45
- <th>Time (UTC)</th>
46
- <th>Tx</th>
45
+ <th>
46
+ Address
47
+ <abbr class="dao-info" title="The wallet address associated with this event (recipient, creator, or initiator depending on event type). Click to view in explorer; click the copy icon to copy the full address.">?</abbr>
48
+ </th>
49
+ <th>
50
+ Block
51
+ <abbr class="dao-info" title="Sui checkpoint sequence number β€” equivalent to a block height in other blockchains. Each checkpoint finalizes a batch of transactions; higher numbers are more recent.">?</abbr>
52
+ </th>
53
+ <th>
54
+ Transaction
55
+ <abbr class="dao-info" title="Transaction digest β€” a unique identifier for the transaction that emitted this event. Click to view in explorer; click the copy icon to copy the full digest.">?</abbr>
56
+ </th>
47
57
  </template>
48
- <tr v-for="ev in events" :key="ev.txDigest + ev.timestampMs">
58
+ <tr v-for="ev in pageEvents" :key="ev.txDigest">
49
59
  <td>
50
60
  <span
51
61
  class="dao-badge"
@@ -56,15 +66,28 @@ function explorerUrl(digest: string): string {
56
66
  }"
57
67
  >{{ eventLabel[ev.type] }}</span>
58
68
  </td>
59
- <td class="dao-mono" style="font-size: 0.72rem">{{ shortAddr(ev.address) }}</td>
60
- <td class="dao-mono" style="font-size: 0.72rem">{{ shortTs(ev.timestampMs) }}</td>
69
+ <td class="dao-mono" style="font-size: 0.72rem; white-space: nowrap">
70
+ <CopyableAddress v-if="ev.address && ev.address !== 'undefined'" :address="ev.address">
71
+ <ExplorerLink :href="suiExplorerUrl('account', ev.address, NETWORK)" :value="ev.address" />
72
+ </CopyableAddress>
73
+ <span v-else>β€”</span>
74
+ </td>
61
75
  <td class="dao-mono" style="font-size: 0.72rem">
62
- <a :href="explorerUrl(ev.txDigest)" target="_blank" rel="noopener" style="color: var(--accent)">
63
- {{ ev.txDigest.slice(0, 8) }}…
64
- </a>
76
+ {{ ev.checkpoint ? '#' + ev.checkpoint : 'β€”' }}
77
+ </td>
78
+ <td class="dao-mono" style="font-size: 0.72rem; white-space: nowrap">
79
+ <CopyableAddress :address="ev.txDigest" label="Copy transaction">
80
+ <ExplorerLink :href="suiExplorerUrl('txblock', ev.txDigest, NETWORK)" :value="ev.txDigest" />
81
+ </CopyableAddress>
65
82
  </td>
66
83
  </tr>
67
84
  </DataTable>
68
85
  <p v-else-if="!loading" class="dao-placeholder">No events found.</p>
86
+
87
+ <div v-if="totalPages > 1" class="dao-pagination">
88
+ <button class="dao-toolbar__btn" :disabled="page === 1" @click="prevPage">← Prev</button>
89
+ <span class="dao-muted" style="font-size: 0.78rem">Page {{ page }} / {{ totalPages }}</span>
90
+ <button class="dao-toolbar__btn" :disabled="page === totalPages" @click="nextPage">Next β†’</button>
91
+ </div>
69
92
  </div>
70
93
  </template>
@@ -5,19 +5,15 @@ import AmountCell from '../components/AmountCell.vue'
5
5
  import { usePlatformConfig } from '../composables/usePlatformConfig.js'
6
6
  import { useTreasury } from '../composables/useTreasury.js'
7
7
  import { useDaoEvents } from '../composables/useDaoEvents.js'
8
+ import { useGates } from '../composables/useGates.js'
9
+ import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
10
+ import { NETWORK } from '../config.js'
8
11
  import type { DaoEvent } from '../composables/useDaoEvents.js'
9
12
 
10
13
  const { config } = usePlatformConfig()
11
14
  const { balance } = useTreasury(() => config.value?.treasury ?? null)
12
15
  const { events, loading: eventsLoading } = useDaoEvents(12)
13
-
14
- function timeAgo(ms: number): string {
15
- const s = Math.floor((Date.now() - ms) / 1000)
16
- if (s < 5) return 'just now'
17
- if (s < 60) return `${s}s ago`
18
- if (s < 3600) return `${Math.floor(s / 60)}m ago`
19
- return `${Math.floor(s / 3600)}h ago`
20
- }
16
+ const { gates, loading: gatesLoading } = useGates()
21
17
 
22
18
  const eventLabel: Record<DaoEvent['type'], string> = {
23
19
  AccessMinted: 'Access sold',
@@ -29,6 +25,9 @@ const eventLabel: Record<DaoEvent['type'], string> = {
29
25
  const commissionPct = computed(() =>
30
26
  config.value ? (config.value.commissionBps / 100).toFixed(2) + '%' : 'β€”',
31
27
  )
28
+
29
+ const accessesMinted = computed(() => events.value.filter((e) => e.type === 'AccessMinted').length)
30
+ const accessesConsumed = computed(() => events.value.filter((e) => e.type === 'AccessConsumed').length)
32
31
  </script>
33
32
 
34
33
  <template>
@@ -44,31 +43,26 @@ const commissionPct = computed(() =>
44
43
  <span class="dao-stat-grid__value dao-mono">{{ commissionPct }}</span>
45
44
 
46
45
  <span class="dao-stat-grid__label">Treasury address</span>
47
- <span
48
- class="dao-stat-grid__value dao-mono"
49
- style="font-size: 0.7rem; word-break: break-all; text-align: left"
50
- :title="config?.treasury"
51
- >
52
- {{ config?.treasury ? config.treasury.slice(0, 10) + '…' + config.treasury.slice(-6) : 'β€”' }}
46
+ <span class="dao-stat-grid__value" style="text-align: left">
47
+ <CopyableAddress v-if="config?.treasury" :address="config.treasury">
48
+ <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
49
+ </CopyableAddress>
50
+ <span v-else class="dao-mono" style="font-size: 0.7rem">β€”</span>
53
51
  </span>
54
52
  </div>
55
53
  </Panel>
56
54
 
57
- <Panel title="Platform Stats">
55
+ <Panel title="Platform Activity">
58
56
  <div class="dao-stat-grid">
59
- <span class="dao-stat-grid__label">Strategy NAV</span>
60
- <span class="dao-stat-grid__value dao-muted">β€”</span>
57
+ <span class="dao-stat-grid__label">Active gates</span>
58
+ <span class="dao-stat-grid__value dao-mono">{{ gatesLoading ? '…' : gates.length }}</span>
61
59
 
62
- <span class="dao-stat-grid__label">Buyback/burn</span>
63
- <span class="dao-stat-grid__value dao-muted">β€”</span>
60
+ <span class="dao-stat-grid__label">Accesses minted</span>
61
+ <span class="dao-stat-grid__value dao-mono">{{ accessesMinted || 'β€”' }}</span>
64
62
 
65
- <span class="dao-stat-grid__label">Fee distributor</span>
66
- <span class="dao-stat-grid__value dao-muted">β€”</span>
63
+ <span class="dao-stat-grid__label">Accesses consumed</span>
64
+ <span class="dao-stat-grid__value dao-mono">{{ accessesConsumed || 'β€”' }}</span>
67
65
  </div>
68
- <p class="dao-muted" style="margin: 8px 0 0; font-size: 0.72rem">
69
- Strategy NAV, buyback and fee distribution data will appear here when
70
- <code>vault_core</code> and <code>vault_fee_distributor</code> are deployed.
71
- </p>
72
66
  </Panel>
73
67
  </div>
74
68
 
@@ -76,10 +70,10 @@ const commissionPct = computed(() =>
76
70
  <Panel title="Recent Activity">
77
71
  <p v-if="eventsLoading" class="dao-muted" style="margin: 0">Loading events…</p>
78
72
  <ul v-else-if="events.length" class="dao-feed">
79
- <li v-for="ev in events" :key="ev.txDigest + ev.timestampMs" class="dao-feed__item">
73
+ <li v-for="ev in events" :key="ev.txDigest" class="dao-feed__item">
80
74
  <span class="dao-feed__dot" />
81
75
  <span class="dao-feed__type">{{ eventLabel[ev.type] }}</span>
82
- <span class="dao-feed__time">{{ timeAgo(ev.timestampMs) }}</span>
76
+ <span class="dao-feed__time">ckpt {{ ev.checkpoint ?? '?' }}</span>
83
77
  </li>
84
78
  </ul>
85
79
  <p v-else class="dao-placeholder">No recent events.</p>
@@ -6,6 +6,8 @@ import DataTable from '../components/DataTable.vue'
6
6
  import { usePlatformConfig } from '../composables/usePlatformConfig.js'
7
7
  import { useTreasury } from '../composables/useTreasury.js'
8
8
  import { useGates } from '../composables/useGates.js'
9
+ import { CopyableAddress, ExplorerLink, suiExplorerUrl } from '@meddleware/ui'
10
+ import { NETWORK } from '../config.js'
9
11
 
10
12
  const { config, loading: cfgLoading, error: cfgErr } = usePlatformConfig()
11
13
  const { balance } = useTreasury(() => config.value?.treasury ?? null)
@@ -19,15 +21,11 @@ function formatPrice(mist: bigint): string {
19
21
  const n = Number(mist) / 1e9
20
22
  return n >= 0.0001 ? n.toFixed(4) + ' SUI' : n.toFixed(9).replace(/0+$/, '') + ' SUI'
21
23
  }
22
-
23
- function shortDate(ms: number): string {
24
- return ms ? new Date(ms).toISOString().slice(0, 10) : 'β€”'
25
- }
26
24
  </script>
27
25
 
28
26
  <template>
29
27
  <div style="display: flex; flex-direction: column; gap: 10px">
30
- <Panel title="Commission Configuration">
28
+ <Panel title="Treasury">
31
29
  <p v-if="cfgLoading" class="dao-muted">Loading…</p>
32
30
  <p v-else-if="cfgErr" class="dao-muted">{{ cfgErr }}</p>
33
31
  <template v-else-if="config">
@@ -35,40 +33,42 @@ function shortDate(ms: number): string {
35
33
  <span class="dao-stat-grid__label">Commission rate</span>
36
34
  <span class="dao-stat-grid__value dao-mono">{{ commissionPct }} ({{ config.commissionBps }} bps)</span>
37
35
 
38
- <span class="dao-stat-grid__label">Treasury address</span>
39
- <span class="dao-stat-grid__value dao-mono" style="font-size: 0.72rem; text-align: left; word-break: break-all">
40
- {{ config.treasury }}
41
- </span>
42
-
43
36
  <span class="dao-stat-grid__label">Current balance</span>
44
37
  <AmountCell class="dao-stat-grid__value" :mist="balance" />
38
+
39
+ <span class="dao-stat-grid__label">Treasury address</span>
40
+ <span class="dao-stat-grid__value" style="text-align: left">
41
+ <CopyableAddress :address="config.treasury">
42
+ <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
43
+ </CopyableAddress>
44
+ </span>
45
45
  </div>
46
46
  </template>
47
47
  <p v-else class="dao-muted">PlatformConfig not loaded.</p>
48
48
  </Panel>
49
49
 
50
- <Panel title="Fee Distribution">
51
- <p class="dao-muted" style="margin: 0; font-size: 0.78rem">
52
- Fee distributor data (buyback/burn ratios, distribution history) will appear here when
53
- <code>vault_fee_distributor</code> is deployed.
54
- </p>
55
- </Panel>
56
-
57
50
  <Panel title="Community Gates">
58
51
  <p v-if="gatesLoading" class="dao-muted">Loading gates…</p>
59
52
  <DataTable v-else-if="gates.length" :empty="'No gates found'">
60
53
  <template #head>
61
54
  <th>Gate name</th>
62
55
  <th>Price</th>
63
- <th>Created</th>
56
+ <th>
57
+ Block
58
+ <abbr class="dao-info" title="Sui checkpoint sequence number at which this gate was created. Equivalent to a block height in other blockchains.">?</abbr>
59
+ </th>
64
60
  <th>Object ID</th>
65
61
  </template>
66
62
  <tr v-for="gate in gates" :key="gate.id">
67
63
  <td>{{ gate.name }}</td>
68
64
  <td class="dao-amount">{{ formatPrice(gate.price) }}</td>
69
- <td class="dao-mono" style="font-size: 0.72rem">{{ shortDate(gate.timestampMs) }}</td>
70
- <td class="dao-mono" style="font-size: 0.7rem">
71
- {{ gate.id.slice(0, 10) }}…{{ gate.id.slice(-6) }}
65
+ <td class="dao-mono" style="font-size: 0.72rem">
66
+ {{ gate.checkpoint ? '#' + gate.checkpoint : 'β€”' }}
67
+ </td>
68
+ <td class="dao-mono" style="font-size: 0.7rem; white-space: nowrap">
69
+ <CopyableAddress :address="gate.id" label="Copy object ID">
70
+ <ExplorerLink :href="suiExplorerUrl('object', gate.id, NETWORK)" :value="gate.id" />
71
+ </CopyableAddress>
72
72
  </td>
73
73
  </tr>
74
74
  </DataTable>