@akta/dao-cli 0.1.0 → 0.1.1
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/package.json +1 -1
- package/src/tui/app.ts +3 -2
- package/src/tui/types.ts +1 -0
- package/src/tui/views/dao.ts +147 -64
- 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
|
|
package/package.json
CHANGED
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,10 +15,10 @@ 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
19
|
|
|
20
20
|
export const daoView: View = {
|
|
21
|
-
async load(ctx: ViewContext): Promise<
|
|
21
|
+
async load(ctx: ViewContext): Promise<LoadResult> {
|
|
22
22
|
const { dao, network, width } = ctx;
|
|
23
23
|
const state = await dao.getGlobalState();
|
|
24
24
|
const ids = getNetworkAppIds(network);
|
|
@@ -53,75 +53,81 @@ export const daoView: View = {
|
|
|
53
53
|
// Asset info fetch failed — skip supply charts
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
56
|
+
// Build structured data for JSON mode
|
|
57
|
+
const data = buildDaoData(state, network, ids, dao.appId, aktaSupply, bonesSupply);
|
|
59
58
|
|
|
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]);
|
|
59
|
+
let lines: string[];
|
|
60
|
+
if (width < 80) {
|
|
61
|
+
lines = renderSingleColumn(state, network, ids, width, aktaSupply, bonesSupply);
|
|
90
62
|
} 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
|
-
|
|
63
|
+
const gridRows: string[][][] = [];
|
|
64
|
+
|
|
65
|
+
// Row 1: DAO info + Assets + Token Supply
|
|
66
|
+
const hasSupply = aktaSupply || bonesSupply;
|
|
67
|
+
const colCount = hasSupply ? 3 : 2;
|
|
68
|
+
const colWidths = splitWidth(width, colCount);
|
|
69
|
+
|
|
70
|
+
const infoContent = renderKV([
|
|
71
|
+
["Network", network],
|
|
72
|
+
["App ID", dao.appId.toString()],
|
|
73
|
+
["Version", state.version ?? "-"],
|
|
74
|
+
["State", state.state !== undefined ? colorState(daoStateLabel(state.state)) : "-"],
|
|
75
|
+
["Wallet", state.wallet ? resolveAppName(state.wallet, network) : "-"],
|
|
76
|
+
]);
|
|
77
|
+
const infoPanel = renderPanel(infoContent, { title: "Akita DAO", width: colWidths[0] });
|
|
78
|
+
|
|
79
|
+
const assetsContent = renderKV([
|
|
80
|
+
["AKTA", state.akitaAssets?.akta?.toString() ?? ids.akta.toString()],
|
|
81
|
+
["BONES", state.akitaAssets?.bones?.toString() ?? ids.bones.toString()],
|
|
82
|
+
["Next Proposal", state.proposalId?.toString() ?? "-"],
|
|
83
|
+
["Action Limit", state.proposalActionLimit?.toString() ?? "-"],
|
|
84
|
+
["Min Rewards", state.minRewardsImpact?.toString() ?? "-"],
|
|
85
|
+
]);
|
|
86
|
+
const assetsPanel = renderPanel(assetsContent, { title: "Assets", width: colWidths[1] });
|
|
87
|
+
|
|
88
|
+
if (hasSupply) {
|
|
89
|
+
const supplyW = colWidths[2];
|
|
90
|
+
const supplyContent = renderSupplyCharts(aktaSupply, bonesSupply, supplyW - 4);
|
|
91
|
+
const supplyPanel = renderPanel(supplyContent, { title: "Token Supply", width: supplyW });
|
|
92
|
+
gridRows.push([infoPanel, assetsPanel, supplyPanel]);
|
|
93
|
+
} else {
|
|
94
|
+
gridRows.push([infoPanel, assetsPanel]);
|
|
116
95
|
}
|
|
117
|
-
|
|
118
|
-
|
|
96
|
+
|
|
97
|
+
// Row 3: App IDs (40%) + Proposal Settings / Revenue Splits stacked (60%)
|
|
98
|
+
const appLines = renderAppTable(state, network);
|
|
99
|
+
const proposalLines = renderProposalSettings(state);
|
|
100
|
+
|
|
101
|
+
if (appLines.length > 0 || proposalLines.length > 0) {
|
|
102
|
+
// ~40/60 split
|
|
103
|
+
const appW = Math.floor((width - 2) * 0.4);
|
|
104
|
+
const rightPanelW = width - appW - 2;
|
|
105
|
+
const revLines = renderRevenueSplits(state, network, rightPanelW - 4);
|
|
106
|
+
|
|
107
|
+
const leftPanel = appLines.length > 0
|
|
108
|
+
? renderPanel(appLines, { title: "App IDs", width: appW })
|
|
109
|
+
: renderPanel([" No app ID data"], { title: "App IDs", width: appW });
|
|
110
|
+
|
|
111
|
+
// Stack Proposal Settings + Revenue Splits into one right column
|
|
112
|
+
const rightPanels: string[] = [];
|
|
113
|
+
if (proposalLines.length > 0) {
|
|
114
|
+
rightPanels.push(...renderPanel(proposalLines, { title: "Proposal Settings", width: rightPanelW }));
|
|
115
|
+
}
|
|
116
|
+
if (revLines.length > 0) {
|
|
117
|
+
if (rightPanels.length > 0) rightPanels.push("");
|
|
118
|
+
rightPanels.push(...renderPanel(revLines, { title: "Revenue Splits", width: rightPanelW }));
|
|
119
|
+
}
|
|
120
|
+
if (rightPanels.length === 0) {
|
|
121
|
+
rightPanels.push(...renderPanel([" No proposal settings"], { title: "Proposal Settings", width: rightPanelW }));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
gridRows.push([leftPanel, rightPanels]);
|
|
119
125
|
}
|
|
120
126
|
|
|
121
|
-
|
|
127
|
+
lines = ["", ...renderPanelGrid(gridRows, { rowGap: 1 })];
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
return
|
|
130
|
+
return { lines, data };
|
|
125
131
|
},
|
|
126
132
|
};
|
|
127
133
|
|
|
@@ -176,6 +182,83 @@ function renderSingleColumn(
|
|
|
176
182
|
return lines;
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
// ── Structured data for JSON mode ───────────────────────────────
|
|
186
|
+
|
|
187
|
+
function buildDaoData(
|
|
188
|
+
state: Partial<AkitaDaoGlobalState>,
|
|
189
|
+
network: AkitaNetwork,
|
|
190
|
+
ids: { akta: bigint; bones: bigint },
|
|
191
|
+
appId: bigint,
|
|
192
|
+
aktaSupply: SupplyInfo | null,
|
|
193
|
+
bonesSupply: SupplyInfo | null,
|
|
194
|
+
) {
|
|
195
|
+
// App ID lists
|
|
196
|
+
const appSections: [string, Record<string, bigint> | undefined][] = [
|
|
197
|
+
["core", state.akitaAppList as Record<string, bigint> | undefined],
|
|
198
|
+
["social", state.akitaSocialAppList as Record<string, bigint> | undefined],
|
|
199
|
+
["plugins", state.pluginAppList as Record<string, bigint> | undefined],
|
|
200
|
+
["other", state.otherAppList as Record<string, bigint> | undefined],
|
|
201
|
+
];
|
|
202
|
+
const apps: Record<string, Record<string, bigint>> = {};
|
|
203
|
+
for (const [category, list] of appSections) {
|
|
204
|
+
if (list) apps[category] = list;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Proposal settings
|
|
208
|
+
const psEntries: [string, { fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint } | undefined][] = [
|
|
209
|
+
["upgradeApp", state.upgradeAppProposalSettings],
|
|
210
|
+
["addPlugin", state.addPluginProposalSettings],
|
|
211
|
+
["removePlugin", state.removePluginProposalSettings],
|
|
212
|
+
["removeExecutePlugin", state.removeExecutePluginProposalSettings],
|
|
213
|
+
["addAllowances", state.addAllowancesProposalSettings],
|
|
214
|
+
["removeAllowances", state.removeAllowancesProposalSettings],
|
|
215
|
+
["newEscrow", state.newEscrowProposalSettings],
|
|
216
|
+
["toggleEscrowLock", state.toggleEscrowLockProposalSettings],
|
|
217
|
+
["updateFields", state.updateFieldsProposalSettings],
|
|
218
|
+
];
|
|
219
|
+
const proposalSettings: Record<string, { fee: bigint; power: bigint; duration: bigint; participation: bigint; approval: bigint }> = {};
|
|
220
|
+
for (const [key, ps] of psEntries) {
|
|
221
|
+
if (ps) proposalSettings[key] = ps;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Revenue splits
|
|
225
|
+
const revenueSplits = state.revenueSplits?.map(([[wallet, escrow], type, value]) => ({
|
|
226
|
+
wallet: wallet.toString(),
|
|
227
|
+
escrow: escrow || undefined,
|
|
228
|
+
type: type === 20 ? "percentage" : type === 30 ? "remainder" : `unknown(${type})`,
|
|
229
|
+
value,
|
|
230
|
+
percentage: type === 20
|
|
231
|
+
? Number(value) / 1000
|
|
232
|
+
: type === 30
|
|
233
|
+
? Number(100_000n - (state.revenueSplits ?? []).reduce((sum, [, t, v]) => t === 20 ? sum + v : sum, 0n)) / 1000
|
|
234
|
+
: undefined,
|
|
235
|
+
})) ?? [];
|
|
236
|
+
|
|
237
|
+
// Token supply
|
|
238
|
+
const tokenSupply: Record<string, { total: bigint; circulating: bigint; decimals: number }> = {};
|
|
239
|
+
if (aktaSupply) tokenSupply.akta = aktaSupply;
|
|
240
|
+
if (bonesSupply) tokenSupply.bones = bonesSupply;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
network,
|
|
244
|
+
appId,
|
|
245
|
+
version: state.version ?? null,
|
|
246
|
+
state: state.state !== undefined ? daoStateLabel(state.state) : null,
|
|
247
|
+
wallet: state.wallet ?? null,
|
|
248
|
+
assets: {
|
|
249
|
+
akta: state.akitaAssets?.akta ?? ids.akta,
|
|
250
|
+
bones: state.akitaAssets?.bones ?? ids.bones,
|
|
251
|
+
nextProposal: state.proposalId ?? null,
|
|
252
|
+
actionLimit: state.proposalActionLimit ?? null,
|
|
253
|
+
minRewardsImpact: state.minRewardsImpact ?? null,
|
|
254
|
+
},
|
|
255
|
+
tokenSupply,
|
|
256
|
+
apps,
|
|
257
|
+
proposalSettings,
|
|
258
|
+
revenueSplits,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
179
262
|
// ── Helpers ────────────────────────────────────────────────────
|
|
180
263
|
|
|
181
264
|
function renderAppTable(state: Partial<AkitaDaoGlobalState>, network: AkitaNetwork): string[] {
|
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(
|