@akta/dao-cli 0.1.0 → 0.1.2
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/README.md +1 -1
- package/images/dao_view_styled.png +0 -0
- package/images/fees_view_styled.png +0 -0
- package/images/proposals_view_styled.png +0 -0
- package/images/wallet_view_styled.png +0 -0
- package/package.json +3 -2
- package/src/commands/state.ts +46 -1
- package/src/tui/app.ts +3 -2
- package/src/tui/types.ts +1 -0
- package/src/tui/views/dao.ts +202 -65
- package/src/tui/views/fees.ts +62 -40
- package/src/tui/views/proposals-list.ts +56 -6
- package/src/tui/views/wallet.ts +130 -5
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ bun install
|
|
|
130
130
|
bun run src/index.ts
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
Uses [`@akta/sdk`](https://www.npmjs.com/package/@akta/sdk) from npm — installed automatically with `bun install`.
|
|
134
134
|
|
|
135
135
|
## License
|
|
136
136
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akta/dao-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Read-only CLI and TUI for querying Akita DAO state on Algorand",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "bun run src/index.ts",
|
|
11
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"style-screenshots": "bun run scripts/style-screenshots.ts"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"@akta/sdk": "0.0.1",
|
package/src/commands/state.ts
CHANGED
|
@@ -9,13 +9,32 @@ import {
|
|
|
9
9
|
formatBigInt,
|
|
10
10
|
daoStateLabel,
|
|
11
11
|
resolveAppName,
|
|
12
|
+
getAppName,
|
|
12
13
|
colorState,
|
|
13
14
|
} from "../formatting";
|
|
14
15
|
|
|
15
16
|
export async function stateCommand(dao: AkitaDaoSDK, network: AkitaNetwork, json: boolean): Promise<void> {
|
|
16
17
|
const state = await dao.getGlobalState();
|
|
17
18
|
|
|
18
|
-
if (json)
|
|
19
|
+
if (json) {
|
|
20
|
+
let pluginProposalSettings: { plugin: bigint; pluginName: string; account: string; fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint }[] = [];
|
|
21
|
+
try {
|
|
22
|
+
const pluginsMap = await dao.client.state.box.plugins.getMap();
|
|
23
|
+
pluginProposalSettings = [...pluginsMap.entries()].map(([key, ps]) => ({
|
|
24
|
+
plugin: key.plugin,
|
|
25
|
+
pluginName: (getAppName(key.plugin, network) ?? key.plugin.toString()).replace(/ Plugin$/, ""),
|
|
26
|
+
account: key.escrow || "Main",
|
|
27
|
+
fee: ps.fee,
|
|
28
|
+
power: ps.power,
|
|
29
|
+
duration: ps.duration,
|
|
30
|
+
participation: ps.participation,
|
|
31
|
+
approval: ps.approval,
|
|
32
|
+
}));
|
|
33
|
+
} catch {
|
|
34
|
+
// Box may be empty or inaccessible
|
|
35
|
+
}
|
|
36
|
+
return printJson({ ...state, pluginProposalSettings });
|
|
37
|
+
}
|
|
19
38
|
|
|
20
39
|
header("Core Settings");
|
|
21
40
|
printKV([
|
|
@@ -38,6 +57,7 @@ export async function stateCommand(dao: AkitaDaoSDK, network: AkitaNetwork, json
|
|
|
38
57
|
printAppLists(state, network);
|
|
39
58
|
printFees(state);
|
|
40
59
|
printProposalSettings(state);
|
|
60
|
+
await printPluginProposalSettings(dao, network);
|
|
41
61
|
printRevenueSplits(state, network);
|
|
42
62
|
}
|
|
43
63
|
|
|
@@ -152,6 +172,31 @@ function printProposalSettings(state: Partial<AkitaDaoGlobalState>): void {
|
|
|
152
172
|
printColumns(["Category", "Fee", "Power", "Duration", "Participation", "Approval"], rows);
|
|
153
173
|
}
|
|
154
174
|
|
|
175
|
+
async function printPluginProposalSettings(dao: AkitaDaoSDK, network: AkitaNetwork): Promise<void> {
|
|
176
|
+
let pluginsMap: Map<{ plugin: bigint; escrow: string }, { fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint }>;
|
|
177
|
+
try {
|
|
178
|
+
pluginsMap = await dao.client.state.box.plugins.getMap();
|
|
179
|
+
} catch {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (pluginsMap.size === 0) return;
|
|
183
|
+
|
|
184
|
+
header("Plugin Proposal Settings");
|
|
185
|
+
const rows = [...pluginsMap.entries()].map(([key, ps]) => {
|
|
186
|
+
const name = (getAppName(key.plugin, network) ?? key.plugin.toString()).replace(/ Plugin$/, "");
|
|
187
|
+
return [
|
|
188
|
+
name,
|
|
189
|
+
key.escrow || "Main",
|
|
190
|
+
formatMicroAlgo(ps.fee),
|
|
191
|
+
formatBigInt(ps.power),
|
|
192
|
+
formatDuration(ps.duration),
|
|
193
|
+
formatBasisPoints(ps.participation),
|
|
194
|
+
formatBasisPoints(ps.approval),
|
|
195
|
+
];
|
|
196
|
+
});
|
|
197
|
+
printColumns(["Plugin", "Account", "Fee", "Power", "Duration", "Participation", "Approval"], rows);
|
|
198
|
+
}
|
|
199
|
+
|
|
155
200
|
function printRevenueSplits(state: Partial<AkitaDaoGlobalState>, network: AkitaNetwork): void {
|
|
156
201
|
if (!state.revenueSplits || state.revenueSplits.length === 0) return;
|
|
157
202
|
|
package/src/tui/app.ts
CHANGED
|
@@ -157,9 +157,10 @@ export async function startTUI(network: AkitaNetwork): Promise<void> {
|
|
|
157
157
|
fixedRight = result.fixedRight;
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
// JSON mode:
|
|
160
|
+
// JSON mode: serialize structured data (or fall back to lines)
|
|
161
161
|
if (state.jsonMode) {
|
|
162
|
-
const
|
|
162
|
+
const jsonData = (result as LoadResult).data ?? lines;
|
|
163
|
+
const jsonStr = JSON.stringify(jsonData, jsonReplacer, 2);
|
|
163
164
|
lines = jsonStr.split("\n");
|
|
164
165
|
fixedRight = undefined;
|
|
165
166
|
}
|
package/src/tui/types.ts
CHANGED
package/src/tui/views/dao.ts
CHANGED
|
@@ -15,14 +15,26 @@ import {
|
|
|
15
15
|
colorState,
|
|
16
16
|
} from "../../formatting";
|
|
17
17
|
import { renderPanel, renderPanelGrid, splitWidth } from "../panels";
|
|
18
|
-
import type { View, ViewContext } from "../types";
|
|
18
|
+
import type { LoadResult, View, ViewContext } from "../types";
|
|
19
|
+
import type { AkitaDaoSDK } from "@akta/sdk/dao";
|
|
20
|
+
|
|
21
|
+
type PluginsMap = Awaited<ReturnType<AkitaDaoSDK["client"]["state"]["box"]["plugins"]["getMap"]>>;
|
|
19
22
|
|
|
20
23
|
export const daoView: View = {
|
|
21
|
-
async load(ctx: ViewContext): Promise<
|
|
24
|
+
async load(ctx: ViewContext): Promise<LoadResult> {
|
|
22
25
|
const { dao, network, width } = ctx;
|
|
23
26
|
const state = await dao.getGlobalState();
|
|
24
27
|
const ids = getNetworkAppIds(network);
|
|
25
28
|
|
|
29
|
+
// Fetch per-plugin proposal settings from box storage
|
|
30
|
+
let pluginsMap: PluginsMap | null = null;
|
|
31
|
+
try {
|
|
32
|
+
pluginsMap = await dao.client.state.box.plugins.getMap();
|
|
33
|
+
if (pluginsMap.size === 0) pluginsMap = null;
|
|
34
|
+
} catch {
|
|
35
|
+
// Box may be empty or inaccessible
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
// Fetch supply data for AKTA & BONES
|
|
27
39
|
let aktaSupply: SupplyInfo | null = null;
|
|
28
40
|
let bonesSupply: SupplyInfo | null = null;
|
|
@@ -53,75 +65,86 @@ export const daoView: View = {
|
|
|
53
65
|
// Asset info fetch failed — skip supply charts
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
68
|
+
// Build structured data for JSON mode
|
|
69
|
+
const data = buildDaoData(state, network, ids, dao.appId, aktaSupply, bonesSupply, pluginsMap);
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const hasSupply = aktaSupply || bonesSupply;
|
|
64
|
-
const colCount = hasSupply ? 3 : 2;
|
|
65
|
-
const colWidths = splitWidth(width, colCount);
|
|
66
|
-
|
|
67
|
-
const infoContent = renderKV([
|
|
68
|
-
["Network", network],
|
|
69
|
-
["App ID", dao.appId.toString()],
|
|
70
|
-
["Version", state.version ?? "-"],
|
|
71
|
-
["State", state.state !== undefined ? colorState(daoStateLabel(state.state)) : "-"],
|
|
72
|
-
["Wallet", state.wallet ? resolveAppName(state.wallet, network) : "-"],
|
|
73
|
-
]);
|
|
74
|
-
const infoPanel = renderPanel(infoContent, { title: "Akita DAO", width: colWidths[0] });
|
|
75
|
-
|
|
76
|
-
const assetsContent = renderKV([
|
|
77
|
-
["AKTA", state.akitaAssets?.akta?.toString() ?? ids.akta.toString()],
|
|
78
|
-
["BONES", state.akitaAssets?.bones?.toString() ?? ids.bones.toString()],
|
|
79
|
-
["Next Proposal", state.proposalId?.toString() ?? "-"],
|
|
80
|
-
["Action Limit", state.proposalActionLimit?.toString() ?? "-"],
|
|
81
|
-
["Min Rewards", state.minRewardsImpact?.toString() ?? "-"],
|
|
82
|
-
]);
|
|
83
|
-
const assetsPanel = renderPanel(assetsContent, { title: "Assets", width: colWidths[1] });
|
|
84
|
-
|
|
85
|
-
if (hasSupply) {
|
|
86
|
-
const supplyW = colWidths[2];
|
|
87
|
-
const supplyContent = renderSupplyCharts(aktaSupply, bonesSupply, supplyW - 4);
|
|
88
|
-
const supplyPanel = renderPanel(supplyContent, { title: "Token Supply", width: supplyW });
|
|
89
|
-
gridRows.push([infoPanel, assetsPanel, supplyPanel]);
|
|
71
|
+
let lines: string[];
|
|
72
|
+
if (width < 80) {
|
|
73
|
+
lines = renderSingleColumn(state, network, ids, width, aktaSupply, bonesSupply, pluginsMap);
|
|
90
74
|
} else {
|
|
91
|
-
gridRows
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
75
|
+
const gridRows: string[][][] = [];
|
|
76
|
+
|
|
77
|
+
// Row 1: DAO info + Assets + Token Supply
|
|
78
|
+
const hasSupply = aktaSupply || bonesSupply;
|
|
79
|
+
const colCount = hasSupply ? 3 : 2;
|
|
80
|
+
const colWidths = splitWidth(width, colCount);
|
|
81
|
+
|
|
82
|
+
const infoContent = renderKV([
|
|
83
|
+
["Network", network],
|
|
84
|
+
["App ID", dao.appId.toString()],
|
|
85
|
+
["Version", state.version ?? "-"],
|
|
86
|
+
["State", state.state !== undefined ? colorState(daoStateLabel(state.state)) : "-"],
|
|
87
|
+
["Wallet", state.wallet ? resolveAppName(state.wallet, network) : "-"],
|
|
88
|
+
]);
|
|
89
|
+
const infoPanel = renderPanel(infoContent, { title: "Akita DAO", width: colWidths[0] });
|
|
90
|
+
|
|
91
|
+
const assetsContent = renderKV([
|
|
92
|
+
["AKTA", state.akitaAssets?.akta?.toString() ?? ids.akta.toString()],
|
|
93
|
+
["BONES", state.akitaAssets?.bones?.toString() ?? ids.bones.toString()],
|
|
94
|
+
["Next Proposal", state.proposalId?.toString() ?? "-"],
|
|
95
|
+
["Action Limit", state.proposalActionLimit?.toString() ?? "-"],
|
|
96
|
+
["Min Rewards", state.minRewardsImpact?.toString() ?? "-"],
|
|
97
|
+
]);
|
|
98
|
+
const assetsPanel = renderPanel(assetsContent, { title: "Assets", width: colWidths[1] });
|
|
99
|
+
|
|
100
|
+
if (hasSupply) {
|
|
101
|
+
const supplyW = colWidths[2];
|
|
102
|
+
const supplyContent = renderSupplyCharts(aktaSupply, bonesSupply, supplyW - 4);
|
|
103
|
+
const supplyPanel = renderPanel(supplyContent, { title: "Token Supply", width: supplyW });
|
|
104
|
+
gridRows.push([infoPanel, assetsPanel, supplyPanel]);
|
|
105
|
+
} else {
|
|
106
|
+
gridRows.push([infoPanel, assetsPanel]);
|
|
116
107
|
}
|
|
117
|
-
|
|
118
|
-
|
|
108
|
+
|
|
109
|
+
// Row 3: App IDs (40%) + Proposal Settings / Revenue Splits stacked (60%)
|
|
110
|
+
const appLines = renderAppTable(state, network);
|
|
111
|
+
const proposalLines = renderProposalSettings(state);
|
|
112
|
+
|
|
113
|
+
if (appLines.length > 0 || proposalLines.length > 0) {
|
|
114
|
+
// ~40/60 split
|
|
115
|
+
const appW = Math.floor((width - 2) * 0.4);
|
|
116
|
+
const rightPanelW = width - appW - 2;
|
|
117
|
+
const revLines = renderRevenueSplits(state, network, rightPanelW - 4);
|
|
118
|
+
|
|
119
|
+
const leftPanel = appLines.length > 0
|
|
120
|
+
? renderPanel(appLines, { title: "App IDs", width: appW })
|
|
121
|
+
: renderPanel([" No app ID data"], { title: "App IDs", width: appW });
|
|
122
|
+
|
|
123
|
+
// Stack Proposal Settings + Plugin Proposal Settings + Revenue Splits into one right column
|
|
124
|
+
const rightPanels: string[] = [];
|
|
125
|
+
if (proposalLines.length > 0) {
|
|
126
|
+
rightPanels.push(...renderPanel(proposalLines, { title: "Proposal Settings", width: rightPanelW }));
|
|
127
|
+
}
|
|
128
|
+
const pluginPsLines = renderPluginProposalSettings(pluginsMap, network);
|
|
129
|
+
if (pluginPsLines.length > 0) {
|
|
130
|
+
if (rightPanels.length > 0) rightPanels.push("");
|
|
131
|
+
rightPanels.push(...renderPanel(pluginPsLines, { title: "Plugin Proposal Settings", width: rightPanelW }));
|
|
132
|
+
}
|
|
133
|
+
if (revLines.length > 0) {
|
|
134
|
+
if (rightPanels.length > 0) rightPanels.push("");
|
|
135
|
+
rightPanels.push(...renderPanel(revLines, { title: "Revenue Splits", width: rightPanelW }));
|
|
136
|
+
}
|
|
137
|
+
if (rightPanels.length === 0) {
|
|
138
|
+
rightPanels.push(...renderPanel([" No proposal settings"], { title: "Proposal Settings", width: rightPanelW }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
gridRows.push([leftPanel, rightPanels]);
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
lines = ["", ...renderPanelGrid(gridRows, { rowGap: 1 })];
|
|
122
145
|
}
|
|
123
146
|
|
|
124
|
-
return
|
|
147
|
+
return { lines, data };
|
|
125
148
|
},
|
|
126
149
|
};
|
|
127
150
|
|
|
@@ -134,6 +157,7 @@ function renderSingleColumn(
|
|
|
134
157
|
width: number,
|
|
135
158
|
aktaSupply: SupplyInfo | null,
|
|
136
159
|
bonesSupply: SupplyInfo | null,
|
|
160
|
+
pluginsMap: PluginsMap | null,
|
|
137
161
|
): string[] {
|
|
138
162
|
const lines: string[] = [""];
|
|
139
163
|
|
|
@@ -167,6 +191,12 @@ function renderSingleColumn(
|
|
|
167
191
|
lines.push(...renderPanel(proposalLines, { title: "Proposal Settings", width }));
|
|
168
192
|
}
|
|
169
193
|
|
|
194
|
+
const pluginPsLines = renderPluginProposalSettings(pluginsMap, network);
|
|
195
|
+
if (pluginPsLines.length > 0) {
|
|
196
|
+
lines.push("");
|
|
197
|
+
lines.push(...renderPanel(pluginPsLines, { title: "Plugin Proposal Settings", width }));
|
|
198
|
+
}
|
|
199
|
+
|
|
170
200
|
const revLines = renderRevenueSplits(state, network, width - 4);
|
|
171
201
|
if (revLines.length > 0) {
|
|
172
202
|
lines.push("");
|
|
@@ -176,6 +206,94 @@ function renderSingleColumn(
|
|
|
176
206
|
return lines;
|
|
177
207
|
}
|
|
178
208
|
|
|
209
|
+
// ── Structured data for JSON mode ───────────────────────────────
|
|
210
|
+
|
|
211
|
+
function buildDaoData(
|
|
212
|
+
state: Partial<AkitaDaoGlobalState>,
|
|
213
|
+
network: AkitaNetwork,
|
|
214
|
+
ids: { akta: bigint; bones: bigint },
|
|
215
|
+
appId: bigint,
|
|
216
|
+
aktaSupply: SupplyInfo | null,
|
|
217
|
+
bonesSupply: SupplyInfo | null,
|
|
218
|
+
pluginsMap: PluginsMap | null,
|
|
219
|
+
) {
|
|
220
|
+
// App ID lists
|
|
221
|
+
const appSections: [string, Record<string, bigint> | undefined][] = [
|
|
222
|
+
["core", state.akitaAppList as Record<string, bigint> | undefined],
|
|
223
|
+
["social", state.akitaSocialAppList as Record<string, bigint> | undefined],
|
|
224
|
+
["plugins", state.pluginAppList as Record<string, bigint> | undefined],
|
|
225
|
+
["other", state.otherAppList as Record<string, bigint> | undefined],
|
|
226
|
+
];
|
|
227
|
+
const apps: Record<string, Record<string, bigint>> = {};
|
|
228
|
+
for (const [category, list] of appSections) {
|
|
229
|
+
if (list) apps[category] = list;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Proposal settings
|
|
233
|
+
const psEntries: [string, { fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint } | undefined][] = [
|
|
234
|
+
["upgradeApp", state.upgradeAppProposalSettings],
|
|
235
|
+
["addPlugin", state.addPluginProposalSettings],
|
|
236
|
+
["removePlugin", state.removePluginProposalSettings],
|
|
237
|
+
["removeExecutePlugin", state.removeExecutePluginProposalSettings],
|
|
238
|
+
["addAllowances", state.addAllowancesProposalSettings],
|
|
239
|
+
["removeAllowances", state.removeAllowancesProposalSettings],
|
|
240
|
+
["newEscrow", state.newEscrowProposalSettings],
|
|
241
|
+
["toggleEscrowLock", state.toggleEscrowLockProposalSettings],
|
|
242
|
+
["updateFields", state.updateFieldsProposalSettings],
|
|
243
|
+
];
|
|
244
|
+
const proposalSettings: Record<string, { fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint }> = {};
|
|
245
|
+
for (const [key, ps] of psEntries) {
|
|
246
|
+
if (ps) proposalSettings[key] = ps;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Revenue splits
|
|
250
|
+
const revenueSplits = state.revenueSplits?.map(([[wallet, escrow], type, value]) => ({
|
|
251
|
+
wallet: wallet.toString(),
|
|
252
|
+
escrow: escrow || undefined,
|
|
253
|
+
type: type === 20 ? "percentage" : type === 30 ? "remainder" : `unknown(${type})`,
|
|
254
|
+
value,
|
|
255
|
+
percentage: type === 20
|
|
256
|
+
? Number(value) / 1000
|
|
257
|
+
: type === 30
|
|
258
|
+
? Number(100_000n - (state.revenueSplits ?? []).reduce((sum, [, t, v]) => t === 20 ? sum + v : sum, 0n)) / 1000
|
|
259
|
+
: undefined,
|
|
260
|
+
})) ?? [];
|
|
261
|
+
|
|
262
|
+
// Token supply
|
|
263
|
+
const tokenSupply: Record<string, { total: bigint; circulating: bigint; decimals: number }> = {};
|
|
264
|
+
if (aktaSupply) tokenSupply.akta = aktaSupply;
|
|
265
|
+
if (bonesSupply) tokenSupply.bones = bonesSupply;
|
|
266
|
+
|
|
267
|
+
return {
|
|
268
|
+
network,
|
|
269
|
+
appId,
|
|
270
|
+
version: state.version ?? null,
|
|
271
|
+
state: state.state !== undefined ? daoStateLabel(state.state) : null,
|
|
272
|
+
wallet: state.wallet ?? null,
|
|
273
|
+
assets: {
|
|
274
|
+
akta: state.akitaAssets?.akta ?? ids.akta,
|
|
275
|
+
bones: state.akitaAssets?.bones ?? ids.bones,
|
|
276
|
+
nextProposal: state.proposalId ?? null,
|
|
277
|
+
actionLimit: state.proposalActionLimit ?? null,
|
|
278
|
+
minRewardsImpact: state.minRewardsImpact ?? null,
|
|
279
|
+
},
|
|
280
|
+
tokenSupply,
|
|
281
|
+
apps,
|
|
282
|
+
proposalSettings,
|
|
283
|
+
pluginProposalSettings: pluginsMap ? [...pluginsMap.entries()].map(([key, ps]) => ({
|
|
284
|
+
plugin: key.plugin,
|
|
285
|
+
pluginName: (getAppName(key.plugin, network) ?? key.plugin.toString()).replace(/ Plugin$/, ""),
|
|
286
|
+
account: key.escrow || "Main",
|
|
287
|
+
fee: ps.fee,
|
|
288
|
+
power: ps.power,
|
|
289
|
+
duration: ps.duration,
|
|
290
|
+
participation: ps.participation,
|
|
291
|
+
approval: ps.approval,
|
|
292
|
+
})) : [],
|
|
293
|
+
revenueSplits,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
179
297
|
// ── Helpers ────────────────────────────────────────────────────
|
|
180
298
|
|
|
181
299
|
function renderAppTable(state: Partial<AkitaDaoGlobalState>, network: AkitaNetwork): string[] {
|
|
@@ -229,6 +347,25 @@ function renderProposalSettings(state: Partial<AkitaDaoGlobalState>): string[] {
|
|
|
229
347
|
return renderColumns(["Cat", "Fee", "Pwr", "Dur", "Part", "Appr"], rows);
|
|
230
348
|
}
|
|
231
349
|
|
|
350
|
+
function renderPluginProposalSettings(pluginsMap: PluginsMap | null, network: AkitaNetwork): string[] {
|
|
351
|
+
if (!pluginsMap || pluginsMap.size === 0) return [];
|
|
352
|
+
|
|
353
|
+
const rows = [...pluginsMap.entries()].map(([key, ps]) => {
|
|
354
|
+
const name = (getAppName(key.plugin, network) ?? key.plugin.toString()).replace(/ Plugin$/, "");
|
|
355
|
+
return [
|
|
356
|
+
name,
|
|
357
|
+
key.escrow || "Main",
|
|
358
|
+
formatMicroAlgo(ps.fee),
|
|
359
|
+
formatBigInt(ps.power),
|
|
360
|
+
formatDuration(ps.duration),
|
|
361
|
+
inlineBar(ps.participation),
|
|
362
|
+
inlineBar(ps.approval),
|
|
363
|
+
];
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return renderColumns(["Plugin", "Account", "Fee", "Pwr", "Dur", "Part", "Appr"], rows);
|
|
367
|
+
}
|
|
368
|
+
|
|
232
369
|
const SPLIT_COLORS = theme.splitColors;
|
|
233
370
|
|
|
234
371
|
function renderRevenueSplits(state: Partial<AkitaDaoGlobalState>, network: AkitaNetwork, barWidth: number): string[] {
|
|
@@ -293,7 +430,7 @@ function renderRevenueSplits(state: Partial<AkitaDaoGlobalState>, network: Akita
|
|
|
293
430
|
|
|
294
431
|
// ── Inline bar for basis-point percentages ─────────────────────
|
|
295
432
|
|
|
296
|
-
const INLINE_BAR_WIDTH =
|
|
433
|
+
const INLINE_BAR_WIDTH = 10;
|
|
297
434
|
|
|
298
435
|
function inlineBar(bp: bigint): string {
|
|
299
436
|
const pct = Number(bp) / 1000;
|
package/src/tui/views/fees.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "../../formatting";
|
|
9
9
|
import { renderPanel, splitWidth } from "../panels";
|
|
10
10
|
import { padEndVisible, visibleLength } from "../../output";
|
|
11
|
-
import type { View, ViewContext } from "../types";
|
|
11
|
+
import type { LoadResult, View, ViewContext } from "../types";
|
|
12
12
|
|
|
13
13
|
const PERCENTAGE_RE = /Percentage|Tax/i;
|
|
14
14
|
const FEE_RE = /Fee$/i;
|
|
@@ -24,60 +24,65 @@ function feeLabel(key: string): string {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const feesView: View = {
|
|
27
|
-
async load(ctx: ViewContext): Promise<
|
|
27
|
+
async load(ctx: ViewContext): Promise<LoadResult> {
|
|
28
28
|
const { dao, width } = ctx;
|
|
29
29
|
const state = await dao.getGlobalState();
|
|
30
30
|
|
|
31
31
|
const groups = collectFeeGroups(state);
|
|
32
|
+
const rawGroups = collectRawFeeGroups(state);
|
|
32
33
|
|
|
33
34
|
if (groups.length === 0) {
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
return {
|
|
36
|
+
lines: [
|
|
37
|
+
"",
|
|
38
|
+
...renderPanel([" No fee data available."], { title: "Fees", width }),
|
|
39
|
+
],
|
|
40
|
+
data: {},
|
|
41
|
+
};
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
let lines: string[];
|
|
40
45
|
if (width < 80) {
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
lines = renderSingleColumn(groups, width);
|
|
47
|
+
} else {
|
|
48
|
+
const cols = 2;
|
|
49
|
+
const colGap = 2;
|
|
50
|
+
const panelGap = 1;
|
|
51
|
+
const [leftW, rightW] = splitWidth(width, cols, colGap);
|
|
52
|
+
|
|
53
|
+
// Compute max key width per grid column
|
|
54
|
+
const colKeyWidths: number[] = Array(cols).fill(0);
|
|
55
|
+
for (let i = 0; i < groups.length; i++) {
|
|
56
|
+
const col = i % cols;
|
|
57
|
+
for (const [key] of groups[i].pairs) {
|
|
58
|
+
colKeyWidths[col] = Math.max(colKeyWidths[col], key.length);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
|
-
}
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// Split groups into independent columns
|
|
63
|
+
const colWidths = [leftW, rightW];
|
|
64
|
+
const columns: string[][] = [[], []];
|
|
65
|
+
for (let i = 0; i < groups.length; i++) {
|
|
66
|
+
const col = i % cols;
|
|
67
|
+
if (columns[col].length > 0) {
|
|
68
|
+
for (let g = 0; g < panelGap; g++) columns[col].push("");
|
|
69
|
+
}
|
|
70
|
+
const content = renderFeeKV(groups[i].pairs, colKeyWidths[col]);
|
|
71
|
+
columns[col].push(...renderPanel(content, { title: groups[i].title, width: colWidths[col] }));
|
|
65
72
|
}
|
|
66
|
-
const content = renderFeeKV(groups[i].pairs, colKeyWidths[col]);
|
|
67
|
-
columns[col].push(...renderPanel(content, { title: groups[i].title, width: colWidths[col] }));
|
|
68
|
-
}
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
// Merge columns side-by-side
|
|
75
|
+
const maxHeight = Math.max(columns[0].length, columns[1].length);
|
|
76
|
+
const gapStr = " ".repeat(colGap);
|
|
77
|
+
lines = [""];
|
|
78
|
+
for (let i = 0; i < maxHeight; i++) {
|
|
79
|
+
const left = i < columns[0].length ? padEndVisible(columns[0][i], leftW) : " ".repeat(leftW);
|
|
80
|
+
const right = i < columns[1].length ? columns[1][i] : "";
|
|
81
|
+
lines.push(left + gapStr + right);
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
return lines;
|
|
85
|
+
return { lines, data: rawGroups };
|
|
81
86
|
},
|
|
82
87
|
};
|
|
83
88
|
|
|
@@ -97,6 +102,23 @@ function renderFeeKV(pairs: [string, string][], keyWidth?: number): string[] {
|
|
|
97
102
|
return pairs.map(([key, value]) => ` ${theme.label(key.padEnd(w))} ${value}`);
|
|
98
103
|
}
|
|
99
104
|
|
|
105
|
+
function collectRawFeeGroups(state: Partial<AkitaDaoGlobalState>): Record<string, Record<string, bigint>> {
|
|
106
|
+
const feeGroups: [string, Record<string, bigint> | undefined][] = [
|
|
107
|
+
["walletFees", state.walletFees as Record<string, bigint> | undefined],
|
|
108
|
+
["socialFees", state.socialFees as Record<string, bigint> | undefined],
|
|
109
|
+
["stakingFees", state.stakingFees as Record<string, bigint> | undefined],
|
|
110
|
+
["subscriptionFees", state.subscriptionFees as Record<string, bigint> | undefined],
|
|
111
|
+
["swapFees", state.swapFees as Record<string, bigint> | undefined],
|
|
112
|
+
["nftFees", state.nftFees as Record<string, bigint> | undefined],
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const result: Record<string, Record<string, bigint>> = {};
|
|
116
|
+
for (const [name, fees] of feeGroups) {
|
|
117
|
+
if (fees) result[name] = fees;
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
|
|
100
122
|
function collectFeeGroups(state: Partial<AkitaDaoGlobalState>): KVGroup[] {
|
|
101
123
|
const feeGroups: [string, Record<string, bigint> | undefined][] = [
|
|
102
124
|
["Wallet Fees", state.walletFees as Record<string, bigint> | undefined],
|
|
@@ -3,7 +3,9 @@ import theme from "../../theme";
|
|
|
3
3
|
import {
|
|
4
4
|
truncateAddress,
|
|
5
5
|
formatTimestamp,
|
|
6
|
+
formatCID,
|
|
6
7
|
proposalStatusLabel,
|
|
8
|
+
proposalActionLabel,
|
|
7
9
|
colorStatus,
|
|
8
10
|
} from "../../formatting";
|
|
9
11
|
import { renderPanel } from "../panels";
|
|
@@ -112,10 +114,13 @@ export const proposalsListView: View = {
|
|
|
112
114
|
if (_cursor >= _proposalIds.length) _cursor = 0;
|
|
113
115
|
|
|
114
116
|
if (_cachedEntries.length === 0) {
|
|
115
|
-
return
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
return {
|
|
118
|
+
lines: [
|
|
119
|
+
"",
|
|
120
|
+
...renderPanel([" No proposals found."], { title: "Proposals", width }),
|
|
121
|
+
],
|
|
122
|
+
data: { proposals: [], selected: null },
|
|
123
|
+
};
|
|
119
124
|
}
|
|
120
125
|
|
|
121
126
|
// Fetch selected proposal detail (cached)
|
|
@@ -138,9 +143,15 @@ export const proposalsListView: View = {
|
|
|
138
143
|
}
|
|
139
144
|
}
|
|
140
145
|
|
|
146
|
+
// Build structured data for JSON mode
|
|
147
|
+
const data = buildProposalsData(_cachedEntries, detailData, selectedId);
|
|
148
|
+
|
|
141
149
|
// Narrow: single column (list, then detail below)
|
|
142
150
|
if (width < 80) {
|
|
143
|
-
return
|
|
151
|
+
return {
|
|
152
|
+
lines: renderSingleColumn(_cachedEntries, detailData, selectedId, network, width),
|
|
153
|
+
data,
|
|
154
|
+
};
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
// Wide: two-panel layout (left scrolls, right fixed)
|
|
@@ -159,10 +170,48 @@ export const proposalsListView: View = {
|
|
|
159
170
|
return {
|
|
160
171
|
lines: ["", ...listPanel],
|
|
161
172
|
fixedRight: ["", ...detailLines],
|
|
173
|
+
data,
|
|
162
174
|
};
|
|
163
175
|
},
|
|
164
176
|
};
|
|
165
177
|
|
|
178
|
+
// ── Structured data for JSON mode ──────────────────────────────
|
|
179
|
+
|
|
180
|
+
function buildProposalsData(
|
|
181
|
+
entries: ListEntry[],
|
|
182
|
+
detailData: ProposalData | null,
|
|
183
|
+
selectedId: bigint | undefined,
|
|
184
|
+
) {
|
|
185
|
+
const proposals = entries.map((e) => ({
|
|
186
|
+
id: e.id,
|
|
187
|
+
status: proposalStatusLabel(e.status),
|
|
188
|
+
creator: e.creator,
|
|
189
|
+
votes: e.votes,
|
|
190
|
+
actionCount: e.actionCount,
|
|
191
|
+
created: e.created,
|
|
192
|
+
}));
|
|
193
|
+
|
|
194
|
+
let selected = null;
|
|
195
|
+
if (detailData && selectedId !== undefined) {
|
|
196
|
+
selected = {
|
|
197
|
+
id: selectedId,
|
|
198
|
+
status: proposalStatusLabel(detailData.status),
|
|
199
|
+
creator: detailData.creator,
|
|
200
|
+
cid: formatCID(detailData.cid),
|
|
201
|
+
created: detailData.created,
|
|
202
|
+
votingTs: detailData.votingTs,
|
|
203
|
+
votes: detailData.votes,
|
|
204
|
+
feesPaid: detailData.feesPaid,
|
|
205
|
+
actions: detailData.actions.map((a) => ({
|
|
206
|
+
...a,
|
|
207
|
+
type: proposalActionLabel(a.type),
|
|
208
|
+
})),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return { proposals, selected };
|
|
213
|
+
}
|
|
214
|
+
|
|
166
215
|
// ── Left panel: compact proposals list ─────────────────────────
|
|
167
216
|
|
|
168
217
|
function renderListPanel(entries: ListEntry[], width: number): string[] {
|
|
@@ -171,11 +220,12 @@ function renderListPanel(entries: ListEntry[], width: number): string[] {
|
|
|
171
220
|
return [
|
|
172
221
|
marker + e.id.toString(),
|
|
173
222
|
colorStatus(proposalStatusLabel(e.status)),
|
|
223
|
+
truncateAddress(e.creator),
|
|
174
224
|
e.actionCount.toString(),
|
|
175
225
|
];
|
|
176
226
|
});
|
|
177
227
|
|
|
178
|
-
const content = renderColumns([" ID", "Status", "
|
|
228
|
+
const content = renderColumns([" ID", "Status", "Proposer", "Actions"], rows);
|
|
179
229
|
return renderPanel(content, { title: `Proposals (${entries.length})`, width });
|
|
180
230
|
}
|
|
181
231
|
|
package/src/tui/views/wallet.ts
CHANGED
|
@@ -192,27 +192,152 @@ export const walletView: View = {
|
|
|
192
192
|
|
|
193
193
|
const selectedAccount = accounts[_accountIdx];
|
|
194
194
|
|
|
195
|
+
const selectedAppId = selectedAccount.escrowName === ""
|
|
196
|
+
? wallet.appId
|
|
197
|
+
: cache.escrows.find(([n]) => n === selectedAccount.escrowName)?.[1].id ?? 0n;
|
|
198
|
+
|
|
199
|
+
// Build structured data for JSON mode
|
|
200
|
+
const data = buildWalletData(cache, network, accounts, selectedAccount, selectedAppId, wallet.appId);
|
|
201
|
+
|
|
195
202
|
if (width < 80) {
|
|
196
|
-
return
|
|
203
|
+
return {
|
|
204
|
+
lines: renderSingleColumn(cache, network, accounts, selectedAccount, wallet.appId),
|
|
205
|
+
data,
|
|
206
|
+
};
|
|
197
207
|
}
|
|
198
208
|
|
|
199
209
|
// Two-panel layout
|
|
200
210
|
const [leftW, rightW] = splitWidth(width, 2);
|
|
201
211
|
|
|
202
|
-
const selectedAppId = selectedAccount.escrowName === ""
|
|
203
|
-
? wallet.appId
|
|
204
|
-
: cache.escrows.find(([n]) => n === selectedAccount.escrowName)?.[1].id ?? 0n;
|
|
205
|
-
|
|
206
212
|
const leftLines = renderLeftPanel(cache, network, accounts, leftW, wallet.appId);
|
|
207
213
|
const rightLines = renderRightPanel(cache, network, selectedAccount, selectedAppId, rightW);
|
|
208
214
|
|
|
209
215
|
return {
|
|
210
216
|
lines: ["", ...leftLines],
|
|
211
217
|
fixedRight: ["", ...rightLines],
|
|
218
|
+
data,
|
|
212
219
|
};
|
|
213
220
|
},
|
|
214
221
|
};
|
|
215
222
|
|
|
223
|
+
// ── Structured data for JSON mode ────────────────────────────────
|
|
224
|
+
|
|
225
|
+
function buildWalletData(
|
|
226
|
+
cache: WalletCache,
|
|
227
|
+
network: AkitaNetwork,
|
|
228
|
+
accounts: { name: string; address: string; escrowName: string }[],
|
|
229
|
+
selectedAccount: { name: string; address: string; escrowName: string },
|
|
230
|
+
selectedAppId: bigint,
|
|
231
|
+
walletAppId: bigint,
|
|
232
|
+
) {
|
|
233
|
+
const gs = cache.globalState;
|
|
234
|
+
|
|
235
|
+
const info = {
|
|
236
|
+
version: gs.version ?? null,
|
|
237
|
+
admin: gs.admin ?? null,
|
|
238
|
+
domain: typeof gs.domain === "string" && gs.domain ? gs.domain : null,
|
|
239
|
+
nickname: typeof gs.nickname === "string" && gs.nickname ? gs.nickname : null,
|
|
240
|
+
dao: gs.akitaDao ?? null,
|
|
241
|
+
factory: typeof gs.factoryApp === "bigint" && (gs.factoryApp as bigint) > 0n ? gs.factoryApp : null,
|
|
242
|
+
referrer: typeof gs.referrer === "string" && !isZeroAddress(gs.referrer) ? gs.referrer : null,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const mainAddr = accounts[0].address;
|
|
246
|
+
const accountsData = accounts.map((acct) => {
|
|
247
|
+
const bal = cache.balances.get(acct.address);
|
|
248
|
+
return {
|
|
249
|
+
name: acct.name,
|
|
250
|
+
appId: acct.escrowName === ""
|
|
251
|
+
? walletAppId
|
|
252
|
+
: cache.escrows.find(([n]) => n === acct.escrowName)?.[1].id ?? null,
|
|
253
|
+
address: acct.address,
|
|
254
|
+
balances: bal ?? null,
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const escrowsData = cache.escrows.map(([name, esc]) => ({
|
|
259
|
+
name,
|
|
260
|
+
appId: esc.id,
|
|
261
|
+
locked: esc.locked,
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
// Selected account details
|
|
265
|
+
const escrowFilter = selectedAccount.escrowName;
|
|
266
|
+
|
|
267
|
+
const filteredPlugins = cache.plugins.filter(([key]) => {
|
|
268
|
+
const parsed = parsePluginKey(key);
|
|
269
|
+
return escrowFilter === "" ? parsed.escrow === "" : parsed.escrow === escrowFilter;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const plugins = filteredPlugins.map(([key, p]) => {
|
|
273
|
+
const { pluginId, caller } = parsePluginKey(key);
|
|
274
|
+
return {
|
|
275
|
+
key,
|
|
276
|
+
pluginId: pluginId ?? null,
|
|
277
|
+
pluginName: pluginId ? getAppName(pluginId, network) ?? null : null,
|
|
278
|
+
caller: caller || null,
|
|
279
|
+
admin: p.admin,
|
|
280
|
+
delegationType: delegationTypeLabel(p.delegationType),
|
|
281
|
+
coverFees: p.coverFees,
|
|
282
|
+
canReclaim: p.canReclaim,
|
|
283
|
+
useExecutionKey: p.useExecutionKey,
|
|
284
|
+
useRounds: p.useRounds,
|
|
285
|
+
cooldown: p.cooldown,
|
|
286
|
+
lastCalled: p.lastCalled,
|
|
287
|
+
start: p.start,
|
|
288
|
+
lastValid: p.lastValid,
|
|
289
|
+
methods: p.methods.map((m) => ({
|
|
290
|
+
name: resolveMethodSelector(m.name),
|
|
291
|
+
cooldown: m.cooldown,
|
|
292
|
+
lastCalled: m.lastCalled,
|
|
293
|
+
})),
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const namedPlugins = cache.namedPlugins.map(([name, np]) => ({
|
|
298
|
+
name,
|
|
299
|
+
plugin: np.plugin,
|
|
300
|
+
caller: np.caller,
|
|
301
|
+
escrow: np.escrow || null,
|
|
302
|
+
}));
|
|
303
|
+
|
|
304
|
+
const filteredAllowances = cache.allowances.filter(([key]) => {
|
|
305
|
+
if (escrowFilter === "") return true;
|
|
306
|
+
return key.includes(escrowFilter);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const allowances = filteredAllowances.map(([key, a]) => ({
|
|
310
|
+
key,
|
|
311
|
+
type: a.type,
|
|
312
|
+
amount: a.amount ?? null,
|
|
313
|
+
spent: a.spent ?? null,
|
|
314
|
+
rate: a.rate ?? null,
|
|
315
|
+
max: a.max ?? null,
|
|
316
|
+
interval: a.interval ?? null,
|
|
317
|
+
}));
|
|
318
|
+
|
|
319
|
+
const executions = cache.executions.map(([key, e]) => ({
|
|
320
|
+
lease: key,
|
|
321
|
+
firstValid: e.firstValid,
|
|
322
|
+
lastValid: e.lastValid,
|
|
323
|
+
}));
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
info,
|
|
327
|
+
accounts: accountsData,
|
|
328
|
+
escrows: escrowsData,
|
|
329
|
+
selectedAccount: {
|
|
330
|
+
name: selectedAccount.name,
|
|
331
|
+
appId: selectedAppId,
|
|
332
|
+
address: selectedAccount.address,
|
|
333
|
+
plugins,
|
|
334
|
+
namedPlugins,
|
|
335
|
+
allowances,
|
|
336
|
+
executions,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
216
341
|
// ── Left panel: Wallet Info + Account List ─────────────────────
|
|
217
342
|
|
|
218
343
|
function renderLeftPanel(
|