@meddleware/treasury-ui 0.0.3 → 0.0.4

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/treasury-ui",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "homepage": "https://docs.meddleware.co.uk/treasury/",
5
5
  "description": "Meddleware treasury console — organisation-level accounting across revenue streams (Sui and beyond). Read-only, desktop-console UI built on @meddleware/ui.",
6
6
  "author": "Meddleware <dev@meddleware.co.uk>",
@@ -44,9 +44,10 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
+ "@meddleware/access-gate-client": "^0.0.1",
47
48
  "@meddleware/design-tokens": "^0.1.7",
48
49
  "@meddleware/ui": "^0.1.28",
49
- "@meddleware/wallet-adapter": "^0.0.10",
50
+ "@meddleware/wallet-adapter": "^0.0.11",
50
51
  "@mysten/sui": "^2.33.1",
51
52
  "vue": "^3.5.43"
52
53
  },
@@ -69,7 +70,7 @@
69
70
  "stylelint-config-standard": "^40.0.0",
70
71
  "typescript": "~6.0.0",
71
72
  "vite": "^8.3.0",
72
- "vitest": "~5.0.1",
73
+ "vitest": "~5.0.2",
73
74
  "vue-eslint-parser": "^10.4.1",
74
75
  "vue-tsc": "~3.3.11"
75
76
  },
package/src/App.vue CHANGED
@@ -2,7 +2,7 @@
2
2
  // Standalone shell for the Treasury SPA. The core UI lives in TreasuryView.vue (also
3
3
  // exported for inline embedding in the dashboard).
4
4
  import { AppHeader, AppFooter, ColorModeControl, CopyrightLine, useColorMode } from '@meddleware/ui'
5
- import { NETWORK } from './config.js'
5
+ import { network } from './config.js'
6
6
  import TreasuryView from './TreasuryView.vue'
7
7
 
8
8
  const { mode, set } = useColorMode('dark')
@@ -17,7 +17,7 @@ const DEV_URL = import.meta.env.VITE_DEV_URL || 'https://dev.meddleware.co.uk/tr
17
17
  <h1 class="brand-title">Meddleware Treasury</h1>
18
18
  </template>
19
19
  <template #actions>
20
- <span class="network-badge">{{ NETWORK }}</span>
20
+ <span class="network-badge">{{ network }}</span>
21
21
  <ColorModeControl :model-value="mode" @update:model-value="set" />
22
22
  </template>
23
23
  </AppHeader>
@@ -17,7 +17,7 @@ import AccountsTab from './tabs/AccountsTab.vue'
17
17
  import ActivityTab from './tabs/ActivityTab.vue'
18
18
  import { useTreasuryActivity } from './composables/useTreasuryActivity.js'
19
19
  import { useEpoch } from './composables/useEpoch.js'
20
- import { NETWORK } from './config.js'
20
+ import { network } from './config.js'
21
21
 
22
22
  const TABS: AppTab[] = [
23
23
  { id: 'overview', label: 'Overview' },
@@ -51,6 +51,6 @@ const { epoch } = useEpoch()
51
51
  <component :is="activeComponent" />
52
52
  </UiTabPanel>
53
53
 
54
- <UiStatusBar :network="NETWORK" :healthy="!eventsError" :epoch="epoch" :last-refresh="lastRefresh" />
54
+ <UiStatusBar :network="network" :healthy="!eventsError" :epoch="epoch" :last-refresh="lastRefresh" />
55
55
  </div>
56
56
  </template>
@@ -0,0 +1,24 @@
1
+ import { getCurrentInstance, onMounted, watch } from 'vue'
2
+ import { network } from '../config.js'
3
+
4
+ /**
5
+ * Load on mount and again whenever the network changes, clearing the previous network's data
6
+ * first. Only inside a component (unit tests call `reload()` directly).
7
+ */
8
+ export function onChainContext(load: () => Promise<void>, reset: () => void): void {
9
+ if (!getCurrentInstance()) return
10
+ onMounted(load)
11
+ watch(network, () => {
12
+ reset()
13
+ void load()
14
+ })
15
+ }
16
+
17
+ /** A guard against a slower, older load overwriting a newer one. */
18
+ export function latest(): () => () => boolean {
19
+ let generation = 0
20
+ return () => {
21
+ const mine = ++generation
22
+ return () => mine === generation
23
+ }
24
+ }
@@ -1,5 +1,6 @@
1
- import { ref, onMounted, getCurrentInstance } from 'vue'
1
+ import { ref } from 'vue'
2
2
  import { getSuiClient } from '../wallet.js'
3
+ import { onChainContext } from './chainContext.js'
3
4
 
4
5
  export function useEpoch() {
5
6
  const epoch = ref<number | null>(null)
@@ -14,8 +15,7 @@ export function useEpoch() {
14
15
  }
15
16
  }
16
17
 
17
- // Auto-load on mount, but only when used inside a component (skips in unit tests).
18
- if (getCurrentInstance()) onMounted(load)
18
+ onChainContext(load, () => (epoch.value = null))
19
19
 
20
20
  return { epoch, reload: load }
21
21
  }
@@ -1,6 +1,10 @@
1
- import { ref, onMounted, getCurrentInstance } from 'vue'
1
+ // Gates the platform treasury administers: its AdminCaps (resilient to event pruning), each merged
2
+ // with its Gate. A gate that cannot be read is left out rather than failing the list.
3
+ import { ref } from 'vue'
4
+ import { fetchAdminCaps, fetchGate, fetchPlatformConfig } from '@meddleware/access-gate-client'
2
5
  import { getSuiClient } from '../wallet.js'
3
- import { PACKAGE_ID, CONFIG_ID } from '../config.js'
6
+ import { requireDeployment } from '../config.js'
7
+ import { latest, onChainContext } from './chainContext.js'
4
8
 
5
9
  export interface Gate {
6
10
  id: string
@@ -14,64 +18,34 @@ export function useGates() {
14
18
  const gates = ref<Gate[]>([])
15
19
  const loading = ref(false)
16
20
  const error = ref<string | null>(null)
21
+ const begin = latest()
17
22
 
18
23
  async function load() {
19
- if (!PACKAGE_ID || !CONFIG_ID) return
24
+ const current = begin()
20
25
  loading.value = true
21
26
  error.value = null
22
27
  try {
28
+ const d = requireDeployment()
23
29
  const client = getSuiClient()
24
-
25
- // Get treasury address from PlatformConfig so we know whose AdminCaps to look for.
26
- // This is resilient to event pruning (the GateCreatedEvent is pruned on testnet
27
- // after ~3 months, making event-based discovery unreliable).
28
- const configRes = await client.getObject({ objectId: CONFIG_ID, include: { json: true } })
29
- const configFields = configRes.object?.json as Record<string, unknown> | null
30
- const treasury = configFields?.treasury as string | undefined
31
- if (!treasury) {
32
- gates.value = []
33
- return
34
- }
35
-
36
- // List AdminCap objects owned by treasury — one per gate the treasury controls.
37
- const { objects } = await client.listOwnedObjects({
38
- owner: treasury,
39
- type: `${PACKAGE_ID}::access_gate::AdminCap`,
40
- include: { json: true },
41
- })
42
-
43
- // Fetch each Gate shared object referenced by the AdminCap's gate_id.
44
- const gateList = await Promise.all(
45
- (objects ?? []).map(async (o: { json?: { gate_id?: unknown } | null; object?: { json?: { gate_id?: unknown } | null } | null }): Promise<Gate | null> => {
46
- const capFields = o?.json ?? o?.object?.json
47
- const gateId = String(capFields?.gate_id ?? '')
48
- if (!gateId || gateId === 'undefined') return null
49
- try {
50
- const gateRes = await client.getObject({ objectId: gateId, include: { json: true } })
51
- const f = gateRes.object?.json as Record<string, unknown> | null
52
- if (!f) return null
53
- return {
54
- id: gateId,
55
- name: String(f.nft_name ?? 'Unnamed Gate'),
56
- price: BigInt(String(f.price_mist ?? '0')),
57
- paused: Boolean(f.paused),
58
- frozen: Boolean(f.frozen),
59
- }
60
- } catch {
61
- return null
62
- }
63
- }),
30
+ const { treasury } = await fetchPlatformConfig(client, d.platformConfigId, d.originalId)
31
+ const caps = await fetchAdminCaps(client, treasury, d.originalId)
32
+ const settled = await Promise.allSettled(caps.map((c) => fetchGate(client, c.gateId, d.originalId)))
33
+ if (!current()) return
34
+ gates.value = settled.flatMap((r) =>
35
+ r.status === 'fulfilled' && r.value
36
+ ? [{ id: r.value.gateId, name: r.value.nftName || 'Unnamed Gate', price: r.value.priceMist, paused: r.value.paused, frozen: r.value.frozen }]
37
+ : [],
64
38
  )
65
- gates.value = gateList.filter((g): g is Gate => g !== null)
66
39
  } catch (e) {
40
+ if (!current()) return
41
+ gates.value = []
67
42
  error.value = e instanceof Error ? e.message : String(e)
68
43
  } finally {
69
- loading.value = false
44
+ if (current()) loading.value = false
70
45
  }
71
46
  }
72
47
 
73
- // Auto-load on mount, but only when used inside a component (skips in unit tests).
74
- if (getCurrentInstance()) onMounted(load)
48
+ onChainContext(load, () => (gates.value = []))
75
49
 
76
50
  return { gates, loading, error, reload: load }
77
51
  }
@@ -1,9 +1,10 @@
1
- // Reads the PlatformConfig shared object once on mount and exposes the treasury
2
- // address + commission rate (bps). Source of truth for the commission model and for
3
- // whose AdminCaps the gate discovery (useGates) looks up.
4
- import { ref, onMounted, getCurrentInstance } from 'vue'
1
+ // Reads the PlatformConfig shared object and exposes the treasury address + commission rate (bps).
2
+ // Source of truth for the commission model and for whose AdminCaps the gate discovery looks up.
3
+ import { ref } from 'vue'
4
+ import { fetchPlatformConfig } from '@meddleware/access-gate-client'
5
5
  import { getSuiClient } from '../wallet.js'
6
- import { CONFIG_ID } from '../config.js'
6
+ import { requireDeployment } from '../config.js'
7
+ import { latest, onChainContext } from './chainContext.js'
7
8
 
8
9
  export interface PlatformConfig {
9
10
  treasury: string
@@ -14,29 +15,27 @@ export function usePlatformConfig() {
14
15
  const config = ref<PlatformConfig | null>(null)
15
16
  const loading = ref(false)
16
17
  const error = ref<string | null>(null)
18
+ const begin = latest()
17
19
 
18
20
  async function load() {
19
- if (!CONFIG_ID) { error.value = 'PlatformConfig ID not configured'; return }
21
+ const current = begin()
20
22
  loading.value = true
21
23
  error.value = null
22
24
  try {
23
- const client = getSuiClient()
24
- const res = await client.getObject({ objectId: CONFIG_ID, include: { json: true } })
25
- const fields = res.object.json as Record<string, unknown> | null
26
- if (!fields) throw new Error('unexpected object structure')
27
- config.value = {
28
- treasury: String(fields.treasury),
29
- commissionBps: Number(fields.commission_bps),
30
- }
25
+ const d = requireDeployment()
26
+ const c = await fetchPlatformConfig(getSuiClient(), d.platformConfigId, d.originalId)
27
+ if (!current()) return
28
+ config.value = { treasury: c.treasury, commissionBps: Number(c.commissionBps) }
31
29
  } catch (e) {
30
+ if (!current()) return
31
+ config.value = null
32
32
  error.value = e instanceof Error ? e.message : String(e)
33
33
  } finally {
34
- loading.value = false
34
+ if (current()) loading.value = false
35
35
  }
36
36
  }
37
37
 
38
- // Auto-load on mount, but only when used inside a component (skips in unit tests).
39
- if (getCurrentInstance()) onMounted(load)
38
+ onChainContext(load, () => (config.value = null))
40
39
 
41
40
  return { config, loading, error, reload: load }
42
41
  }
@@ -1,12 +1,17 @@
1
- import { ref, onMounted, getCurrentInstance } from 'vue'
1
+ // Recent access_gate mints and consumes, newest first, typed by @meddleware/access-gate-client.
2
+ // Read from the indexer when VITE_INDEXER_URL is set (display data; falls back to the full node).
3
+ import { ref } from 'vue'
4
+ import { listAccessGateEvents, type EventCursor } from '@meddleware/access-gate-client'
2
5
  import { getSuiClient } from '../wallet.js'
3
- import { PACKAGE_ID } from '../config.js'
6
+ import { INDEXER_URL, network, requireDeployment } from '../config.js'
7
+ import { latest, onChainContext } from './chainContext.js'
4
8
 
5
9
  export interface TreasuryEvent {
6
- type: 'AccessMinted' | 'AccessConsumed' | 'AccessBurned'
10
+ type: 'AccessMinted' | 'AccessConsumed'
7
11
  txDigest: string
8
12
  checkpoint: string | null
9
- address?: string
13
+ /** The buyer (or airdrop recipient) of a mint; the NFT owner who spent a use. */
14
+ address: string
10
15
  }
11
16
 
12
17
  export function useTreasuryActivity(limit = 20) {
@@ -14,62 +19,52 @@ export function useTreasuryActivity(limit = 20) {
14
19
  const loading = ref(false)
15
20
  const error = ref<string | null>(null)
16
21
  const lastRefresh = ref<Date | null>(null)
22
+ /** Oldest checkpoint the indexer covers, when the list came from it. */
23
+ const indexedFromCheckpoint = ref<string | null>(null)
24
+ const begin = latest()
17
25
 
18
26
  async function load() {
19
- if (!PACKAGE_ID) return
27
+ const current = begin()
20
28
  loading.value = true
21
29
  error.value = null
22
30
  try {
23
- const client = getSuiClient()
24
- const eventTypes = [
25
- `${PACKAGE_ID}::access_gate::AccessMintedEvent`,
26
- `${PACKAGE_ID}::access_gate::AccessConsumedEvent`,
27
- ]
28
-
29
- const results = await Promise.allSettled(
30
- eventTypes.map((t) =>
31
- client.listEvents({ filter: { eventType: t }, limit, order: 'descending' }),
32
- ),
33
- )
34
-
35
- const all: TreasuryEvent[] = []
36
- for (const r of results) {
37
- if (r.status !== 'fulfilled') continue
38
- for (const e of r.value.events) {
39
- const typeName = e.eventType.split('::').pop() ?? ''
40
- const label =
41
- typeName === 'AccessMintedEvent' ? 'AccessMinted'
42
- : typeName === 'AccessConsumedEvent' ? 'AccessConsumed'
43
- : 'AccessBurned'
44
- const f = (e.json ?? {}) as Record<string, unknown>
45
- all.push({
46
- type: label as TreasuryEvent['type'],
47
- txDigest: e.transactionDigest,
48
- checkpoint: e.checkpoint ?? null,
49
- address: String(f.recipient ?? f.creator ?? f.sender ?? ''),
50
- })
31
+ const d = requireDeployment()
32
+ const out: TreasuryEvent[] = []
33
+ let cursor: EventCursor | null = null
34
+ let from: string | null = null
35
+ // Bounded: each call scans at most a few full-node pages; ten calls cover any realistic limit.
36
+ for (let calls = 0; calls < 10; calls++) {
37
+ const page = await listAccessGateEvents(getSuiClient(), {
38
+ originalId: d.originalId,
39
+ kinds: ['AccessMinted', 'AccessConsumed'],
40
+ limit: Math.min(100, limit - out.length),
41
+ cursor,
42
+ indexer: INDEXER_URL ? { url: INDEXER_URL, network: network.value } : undefined,
43
+ })
44
+ for (const e of page.events) {
45
+ if (e.kind === 'AccessMinted') out.push({ type: e.kind, txDigest: e.txDigest, checkpoint: e.checkpoint, address: e.recipient })
46
+ else if (e.kind === 'AccessConsumed') out.push({ type: e.kind, txDigest: e.txDigest, checkpoint: e.checkpoint, address: e.consumer })
51
47
  }
48
+ from = page.indexedFromCheckpoint ?? from
49
+ cursor = page.cursor
50
+ if (!cursor || out.length >= limit) break
52
51
  }
53
-
54
- // Sort globally by checkpoint descending so the merged list is newest-first across all types.
55
- all.sort((a, b) => {
56
- const ca = BigInt(a.checkpoint ?? '0')
57
- const cb = BigInt(b.checkpoint ?? '0')
58
- if (cb > ca) return 1
59
- if (cb < ca) return -1
60
- return 0
61
- })
62
- events.value = all.slice(0, limit)
52
+ if (!current()) return
53
+ events.value = out.slice(0, limit)
54
+ indexedFromCheckpoint.value = from
63
55
  lastRefresh.value = new Date()
64
56
  } catch (e) {
57
+ if (!current()) return
65
58
  error.value = e instanceof Error ? e.message : String(e)
66
59
  } finally {
67
- loading.value = false
60
+ if (current()) loading.value = false
68
61
  }
69
62
  }
70
63
 
71
- // Auto-load on mount, but only when used inside a component (skips in unit tests).
72
- if (getCurrentInstance()) onMounted(load)
64
+ onChainContext(load, () => {
65
+ events.value = []
66
+ indexedFromCheckpoint.value = null
67
+ })
73
68
 
74
- return { events, loading, error, lastRefresh, reload: load }
69
+ return { events, loading, error, lastRefresh, indexedFromCheckpoint, reload: load }
75
70
  }
package/src/config.ts CHANGED
@@ -1,31 +1,25 @@
1
- // Build-time configuration. All chain ids are read from VITE_* env with working
2
- // testnet defaults; mainnet ids stay empty until deployment (set the _MAINNET vars —
3
- // no code change needed). PACKAGE_ID / CONFIG_ID / RPC_URL resolve to the active network.
4
- export type Network = 'testnet' | 'mainnet'
1
+ // Chain configuration. The network is wallet-adapter's shared runtime selector — the one source for
2
+ // the client, the access_gate ids, explorer links and the status bar. The ids come from
3
+ // @meddleware/access-gate-client/deployments, generated from access-gate-sui's published records.
4
+ import { computed } from 'vue'
5
+ import { useNetwork } from '@meddleware/wallet-adapter'
6
+ import { accessGateDeployment, type AccessGateDeployment } from '@meddleware/access-gate-client/deployments'
7
+ import type { SuiNetwork } from '@meddleware/ui'
5
8
 
6
- export const NETWORK: Network = (import.meta.env.VITE_NETWORK as Network) || 'testnet'
9
+ /** The active network (read-only ref; the standalone build selects `VITE_NETWORK` in main.ts). */
10
+ export const network = useNetwork().network
7
11
 
8
- export const RPC_URLS: Record<Network, string> = {
9
- testnet: import.meta.env.VITE_RPC_TESTNET || 'https://fullnode.testnet.sui.io:443',
10
- mainnet: import.meta.env.VITE_RPC_MAINNET || 'https://fullnode.mainnet.sui.io:443',
11
- }
12
-
13
- /** Published access_gate package ID. Governs which events/objects are queried. */
14
- export const ACCESS_GATE_PACKAGE_ID: Record<Network, string> = {
15
- testnet:
16
- import.meta.env.VITE_ACCESS_GATE_PACKAGE_ID_TESTNET ||
17
- '0x1a81ca177db039585e575beeeee4759466e55910e936a6733e38dbb65025eea4',
18
- mainnet: import.meta.env.VITE_ACCESS_GATE_PACKAGE_ID_MAINNET || '',
19
- }
12
+ /** SuiVision has no localnet; nothing is listed there (no deployment), so testnet links are inert. */
13
+ export const explorerNetwork = computed<SuiNetwork>(() => (network.value === 'mainnet' ? 'mainnet' : 'testnet'))
20
14
 
21
- /** PlatformConfig shared object ID — holds commission_bps + treasury address. */
22
- export const PLATFORM_CONFIG_ID: Record<Network, string> = {
23
- testnet:
24
- import.meta.env.VITE_PLATFORM_CONFIG_ID_TESTNET ||
25
- '0xe3b949cabe9a0574c03dfc924fb3f96e6f959f2bb86d053ed6229a241c3a23f7',
26
- mainnet: import.meta.env.VITE_PLATFORM_CONFIG_ID_MAINNET || '',
15
+ /**
16
+ * The access_gate deployment on the active network.
17
+ *
18
+ * @throws {Error} if none is recorded for it (e.g. mainnet before launch, or localnet).
19
+ */
20
+ export function requireDeployment(): AccessGateDeployment {
21
+ return accessGateDeployment(network.value)
27
22
  }
28
23
 
29
- export const PACKAGE_ID = ACCESS_GATE_PACKAGE_ID[NETWORK]
30
- export const CONFIG_ID = PLATFORM_CONFIG_ID[NETWORK]
31
- export const RPC_URL = RPC_URLS[NETWORK]
24
+ /** Optional read-indexer (display data only; reads fall back to the full node). */
25
+ export const INDEXER_URL: string = import.meta.env.VITE_INDEXER_URL || ''
package/src/env.d.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
3
  interface ImportMetaEnv {
4
+ /** Standalone build only: the network main.ts selects (embedded views follow the host's selector). */
4
5
  readonly VITE_NETWORK?: string
5
- readonly VITE_RPC_TESTNET?: string
6
- readonly VITE_RPC_MAINNET?: string
7
- readonly VITE_ACCESS_GATE_PACKAGE_ID_TESTNET?: string
8
- readonly VITE_ACCESS_GATE_PACKAGE_ID_MAINNET?: string
9
- readonly VITE_PLATFORM_CONFIG_ID_TESTNET?: string
10
- readonly VITE_PLATFORM_CONFIG_ID_MAINNET?: string
6
+ /** Optional read-indexer base URL (display data; reads fall back to the full node). */
7
+ readonly VITE_INDEXER_URL?: string
8
+ readonly VITE_DOCS_URL?: string
9
+ readonly VITE_DEV_URL?: string
11
10
  }
12
11
 
13
12
  interface ImportMeta {
package/src/main.ts CHANGED
@@ -7,7 +7,13 @@ import '@meddleware/ui/base.css'
7
7
  import './styles/qt.css'
8
8
  import { createApp } from 'vue'
9
9
  import { useSeason } from '@meddleware/ui'
10
+ import { useNetwork } from '@meddleware/wallet-adapter'
10
11
  import App from './App.vue'
11
12
 
13
+ // The standalone build targets one network; select it in the shared selector so the client, the
14
+ // ids, the explorer links and the status bar all agree. (Embedded, the host's selector rules.)
15
+ const built = import.meta.env.VITE_NETWORK || 'testnet'
16
+ if (built === 'testnet' || built === 'mainnet') useNetwork().setNetwork(built)
17
+
12
18
  useSeason()
13
19
  createApp(App).mount('#app')
package/src/styles/qt.css CHANGED
@@ -7,7 +7,7 @@
7
7
  * UiActivityFeed/UiActivityItem). What remains here are small text/layout
8
8
  * utilities that are specific to this console's tabs.
9
9
  *
10
- * Applied globally via DaoView.vue (so it works when embedded as a library).
10
+ * Applied globally via TreasuryView.vue (so it works when embedded as a library).
11
11
  */
12
12
 
13
13
  /* ── Shell layout ────────────────────────────────────────────────────────── */
@@ -15,7 +15,7 @@ import {
15
15
  UiStatRow,
16
16
  UiDataTable,
17
17
  } from '@meddleware/ui'
18
- import { NETWORK } from '../config.js'
18
+ import { explorerNetwork } from '../config.js'
19
19
 
20
20
  const { config, loading: cfgLoading, error: cfgErr } = usePlatformConfig()
21
21
  const { balance } = useTreasury(() => config.value?.treasury ?? null)
@@ -42,7 +42,7 @@ function formatPrice(mist: bigint): string {
42
42
  <UiStatRow label="Current balance"><AmountCell :mist="balance" /></UiStatRow>
43
43
  <UiStatRow label="Treasury address" align="left">
44
44
  <CopyableAddress :address="config.treasury">
45
- <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
45
+ <ExplorerLink :href="suiExplorerUrl('account', config.treasury, explorerNetwork)" :value="config.treasury" />
46
46
  </CopyableAddress>
47
47
  </UiStatRow>
48
48
  </UiStatGrid>
@@ -63,7 +63,7 @@ function formatPrice(mist: bigint): string {
63
63
  <td class="dao-amount">{{ formatPrice(gate.price) }}</td>
64
64
  <td class="dao-mono dao-mono--sm dao-nowrap">
65
65
  <CopyableAddress :address="gate.id" label="Copy object ID">
66
- <ExplorerLink :href="suiExplorerUrl('object', gate.id, NETWORK)" :value="gate.id" />
66
+ <ExplorerLink :href="suiExplorerUrl('object', gate.id, explorerNetwork)" :value="gate.id" />
67
67
  </CopyableAddress>
68
68
  </td>
69
69
  </tr>
@@ -11,10 +11,10 @@ import {
11
11
  UiBadge,
12
12
  UiToolbarButton,
13
13
  } from '@meddleware/ui'
14
- import { NETWORK } from '../config.js'
14
+ import { explorerNetwork } from '../config.js'
15
15
  import type { TreasuryEvent } from '../composables/useTreasuryActivity.js'
16
16
 
17
- const { events, loading, error, reload } = useTreasuryActivity(200)
17
+ const { events, loading, error, reload, indexedFromCheckpoint } = useTreasuryActivity(200)
18
18
 
19
19
  const PAGE_SIZE = 20
20
20
  const page = ref(1)
@@ -30,7 +30,6 @@ function nextPage() { if (page.value < totalPages.value) page.value++ }
30
30
  const eventLabel: Record<TreasuryEvent['type'], string> = {
31
31
  AccessMinted: 'Access Sold',
32
32
  AccessConsumed: 'Access Used',
33
- AccessBurned: 'Access Burned',
34
33
  }
35
34
 
36
35
  function badgeVariant(type: TreasuryEvent['type']): 'active' | 'closed' {
@@ -47,13 +46,14 @@ function badgeVariant(type: TreasuryEvent['type']): 'active' | 'closed' {
47
46
  </header>
48
47
 
49
48
  <p v-if="error" class="dao-muted">{{ error }}</p>
49
+ <p v-if="indexedFromCheckpoint" class="dao-muted">Indexed since checkpoint {{ indexedFromCheckpoint }}.</p>
50
50
 
51
51
  <UiDataTable v-if="pageEvents.length || loading" :empty="'No events'">
52
52
  <template #head>
53
53
  <th>Type</th>
54
54
  <th>
55
55
  Address
56
- <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>
56
+ <abbr class="dao-info" title="The buyer of a sold pass, or the holder who used one. Click to view in explorer; click the copy icon to copy the full address.">?</abbr>
57
57
  </th>
58
58
  <th>
59
59
  Block
@@ -68,7 +68,7 @@ function badgeVariant(type: TreasuryEvent['type']): 'active' | 'closed' {
68
68
  <td><UiBadge :variant="badgeVariant(ev.type)">{{ eventLabel[ev.type] }}</UiBadge></td>
69
69
  <td class="dao-mono activity__cell dao-nowrap">
70
70
  <CopyableAddress v-if="ev.address && ev.address !== 'undefined'" :address="ev.address">
71
- <ExplorerLink :href="suiExplorerUrl('account', ev.address, NETWORK)" :value="ev.address" />
71
+ <ExplorerLink :href="suiExplorerUrl('account', ev.address, explorerNetwork)" :value="ev.address" />
72
72
  </CopyableAddress>
73
73
  <span v-else>—</span>
74
74
  </td>
@@ -77,7 +77,7 @@ function badgeVariant(type: TreasuryEvent['type']): 'active' | 'closed' {
77
77
  </td>
78
78
  <td class="dao-mono activity__cell dao-nowrap">
79
79
  <CopyableAddress :address="ev.txDigest" label="Copy transaction">
80
- <ExplorerLink :href="suiExplorerUrl('txblock', ev.txDigest, NETWORK)" :value="ev.txDigest" />
80
+ <ExplorerLink :href="suiExplorerUrl('txblock', ev.txDigest, explorerNetwork)" :value="ev.txDigest" />
81
81
  </CopyableAddress>
82
82
  </td>
83
83
  </tr>
@@ -17,7 +17,7 @@ import {
17
17
  UiActivityFeed,
18
18
  UiActivityItem,
19
19
  } from '@meddleware/ui'
20
- import { NETWORK } from '../config.js'
20
+ import { explorerNetwork } from '../config.js'
21
21
  import type { TreasuryEvent } from '../composables/useTreasuryActivity.js'
22
22
 
23
23
  const { config } = usePlatformConfig()
@@ -28,7 +28,6 @@ const { gates, loading: gatesLoading } = useGates()
28
28
  const eventLabel: Record<TreasuryEvent['type'], string> = {
29
29
  AccessMinted: 'Access sold',
30
30
  AccessConsumed: 'Access used',
31
- AccessBurned: 'Access burned',
32
31
  }
33
32
 
34
33
  const commissionPct = computed(() =>
@@ -49,7 +48,7 @@ const accessesConsumed = computed(() => events.value.filter((e) => e.type === 'A
49
48
  <UiStatRow label="Commission rate">{{ commissionPct }}</UiStatRow>
50
49
  <UiStatRow label="Treasury address" align="left">
51
50
  <CopyableAddress v-if="config?.treasury" :address="config.treasury">
52
- <ExplorerLink :href="suiExplorerUrl('account', config.treasury, NETWORK)" :value="config.treasury" />
51
+ <ExplorerLink :href="suiExplorerUrl('account', config.treasury, explorerNetwork)" :value="config.treasury" />
53
52
  </CopyableAddress>
54
53
  <span v-else class="dao-mono dao-mono--sm">—</span>
55
54
  </UiStatRow>