@akta/dao-cli 0.1.0
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 +137 -0
- 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/install.sh +19 -0
- package/package.json +33 -0
- package/src/commands/info.ts +33 -0
- package/src/commands/proposals.ts +133 -0
- package/src/commands/state.ts +167 -0
- package/src/commands/wallet.ts +356 -0
- package/src/formatting.ts +659 -0
- package/src/index.ts +188 -0
- package/src/output.ts +232 -0
- package/src/sdk.ts +37 -0
- package/src/theme.ts +73 -0
- package/src/tui/app.ts +366 -0
- package/src/tui/input.ts +85 -0
- package/src/tui/panels.ts +133 -0
- package/src/tui/renderer.ts +126 -0
- package/src/tui/terminal.ts +66 -0
- package/src/tui/types.ts +74 -0
- package/src/tui/views/dao.ts +338 -0
- package/src/tui/views/fees.ts +164 -0
- package/src/tui/views/proposal-detail.ts +331 -0
- package/src/tui/views/proposals-list.ts +213 -0
- package/src/tui/views/wallet.ts +560 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import type { AkitaDaoSDK } from "@akta/sdk";
|
|
2
|
+
import { getNetworkAppIds, type AkitaNetwork } from "@akta/sdk";
|
|
3
|
+
import algosdk from "algosdk";
|
|
4
|
+
import { printJson, printColumns, header, printKV, printPluginCard } from "../output";
|
|
5
|
+
import {
|
|
6
|
+
truncateAddress,
|
|
7
|
+
formatBigInt,
|
|
8
|
+
formatTimestamp,
|
|
9
|
+
isZeroAddress,
|
|
10
|
+
camelToLabel,
|
|
11
|
+
resolveAppName,
|
|
12
|
+
getAppName,
|
|
13
|
+
delegationTypeLabel,
|
|
14
|
+
formatDuration,
|
|
15
|
+
colorBool,
|
|
16
|
+
} from "../formatting";
|
|
17
|
+
|
|
18
|
+
// Fields to hide from wallet info display
|
|
19
|
+
const HIDDEN_WALLET_FIELDS = new Set([
|
|
20
|
+
"spendingAddress",
|
|
21
|
+
"currentPlugin",
|
|
22
|
+
"rekeyIndex",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
async function getWallet(dao: AkitaDaoSDK) {
|
|
26
|
+
return await dao.getWallet();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function walletInfo(dao: AkitaDaoSDK, network: AkitaNetwork, json: boolean): Promise<void> {
|
|
30
|
+
const wallet = await getWallet(dao);
|
|
31
|
+
const state = await wallet.getGlobalState();
|
|
32
|
+
|
|
33
|
+
if (json) return printJson(state);
|
|
34
|
+
|
|
35
|
+
header("Wallet Info");
|
|
36
|
+
const pairs: [string, unknown][] = [];
|
|
37
|
+
|
|
38
|
+
for (const [k, v] of Object.entries(state)) {
|
|
39
|
+
if (HIDDEN_WALLET_FIELDS.has(k)) continue;
|
|
40
|
+
|
|
41
|
+
// Hide referrer if zero address
|
|
42
|
+
if (k === "referrer" && typeof v === "string" && isZeroAddress(v)) continue;
|
|
43
|
+
|
|
44
|
+
const label = camelToLabel(k);
|
|
45
|
+
|
|
46
|
+
if (v === undefined || v === null) {
|
|
47
|
+
pairs.push([label, "not set"]);
|
|
48
|
+
} else if (v instanceof Uint8Array) {
|
|
49
|
+
const hex = Buffer.from(v).toString("hex");
|
|
50
|
+
pairs.push([label, hex || "not set"]);
|
|
51
|
+
} else if (typeof v === "bigint") {
|
|
52
|
+
// Show app names for known app ID fields
|
|
53
|
+
if (k === "akitaDao" || k === "escrowFactory" || k === "factoryApp" || k === "revocation") {
|
|
54
|
+
pairs.push([label, v > 0n ? resolveAppName(v, network) : "not set"]);
|
|
55
|
+
} else if (k === "lastUserInteraction" || k === "lastChange") {
|
|
56
|
+
pairs.push([label, formatTimestamp(v)]);
|
|
57
|
+
} else {
|
|
58
|
+
pairs.push([label, v.toString()]);
|
|
59
|
+
}
|
|
60
|
+
} else if (typeof v === "string") {
|
|
61
|
+
pairs.push([label, v || "not set"]);
|
|
62
|
+
} else if (typeof v === "object" && v !== null) {
|
|
63
|
+
// PluginKey or other structs - skip complex objects
|
|
64
|
+
continue;
|
|
65
|
+
} else {
|
|
66
|
+
pairs.push([label, String(v)]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
printKV(pairs);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function walletPlugins(dao: AkitaDaoSDK, network: AkitaNetwork, json: boolean): Promise<void> {
|
|
74
|
+
const wallet = await getWallet(dao);
|
|
75
|
+
const plugins = await wallet.getPlugins();
|
|
76
|
+
|
|
77
|
+
const entries = Array.from(plugins.entries());
|
|
78
|
+
|
|
79
|
+
if (json) {
|
|
80
|
+
const arr = entries.map(([key, info]) => ({ key, ...info }));
|
|
81
|
+
return printJson(arr);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
header("Installed Plugins");
|
|
85
|
+
|
|
86
|
+
if (entries.length === 0) {
|
|
87
|
+
console.log(" No plugins installed.");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const [key, info] of entries) {
|
|
92
|
+
const { pluginId, caller, escrow } = parsePluginKey(key);
|
|
93
|
+
|
|
94
|
+
const pluginName = pluginId ? getAppName(pluginId, network) : undefined;
|
|
95
|
+
const pluginLabel = pluginName
|
|
96
|
+
? `${pluginName} (${pluginId})`
|
|
97
|
+
: pluginId?.toString() ?? key;
|
|
98
|
+
|
|
99
|
+
const pairs: [string, string][] = [
|
|
100
|
+
["Caller", caller === "" ? "(global)" : truncateAddress(caller)],
|
|
101
|
+
];
|
|
102
|
+
if (escrow) pairs.push(["Escrow", escrow]);
|
|
103
|
+
pairs.push(
|
|
104
|
+
["Admin", colorBool(info.admin)],
|
|
105
|
+
["Delegation", delegationTypeLabel(info.delegationType)],
|
|
106
|
+
["Cover Fees", colorBool(info.coverFees)],
|
|
107
|
+
["Can Reclaim", colorBool(info.canReclaim)],
|
|
108
|
+
["Use Exec Key", colorBool(info.useExecutionKey)],
|
|
109
|
+
["Use Rounds", colorBool(info.useRounds)],
|
|
110
|
+
);
|
|
111
|
+
if (info.cooldown > 0n) {
|
|
112
|
+
pairs.push(["Cooldown", formatDuration(info.cooldown)]);
|
|
113
|
+
}
|
|
114
|
+
if (info.lastCalled > 0n) {
|
|
115
|
+
pairs.push(["Last Called", formatTimestamp(info.lastCalled)]);
|
|
116
|
+
}
|
|
117
|
+
if (info.start > 0n) {
|
|
118
|
+
pairs.push(["Start", formatTimestamp(info.start)]);
|
|
119
|
+
}
|
|
120
|
+
if (info.lastValid < BigInt("18446744073709551615")) {
|
|
121
|
+
pairs.push(["Last Valid", info.useRounds ? info.lastValid.toString() : formatTimestamp(info.lastValid)]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Method restrictions as hex selectors
|
|
125
|
+
const methods = info.methods.length > 0
|
|
126
|
+
? info.methods.map((m) => Buffer.from(m.name).toString("hex"))
|
|
127
|
+
: undefined;
|
|
128
|
+
|
|
129
|
+
printPluginCard({ name: pluginLabel, pairs, methods });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Parse the concatenated plugin key string back into its components */
|
|
134
|
+
function parsePluginKey(key: string): { pluginId: bigint | null; caller: string; escrow: string } {
|
|
135
|
+
let digitEnd = 0;
|
|
136
|
+
while (digitEnd < key.length && key[digitEnd] >= "0" && key[digitEnd] <= "9") {
|
|
137
|
+
digitEnd++;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (digitEnd === 0) {
|
|
141
|
+
return { pluginId: null, caller: "", escrow: key };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const pluginId = BigInt(key.slice(0, digitEnd));
|
|
145
|
+
const rest = key.slice(digitEnd);
|
|
146
|
+
|
|
147
|
+
if (rest.length >= 58) {
|
|
148
|
+
return {
|
|
149
|
+
pluginId,
|
|
150
|
+
caller: rest.slice(0, 58),
|
|
151
|
+
escrow: rest.slice(58),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { pluginId, caller: rest, escrow: "" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function walletNamedPlugins(dao: AkitaDaoSDK, json: boolean): Promise<void> {
|
|
159
|
+
const wallet = await getWallet(dao);
|
|
160
|
+
const named = await wallet.getNamedPlugins();
|
|
161
|
+
|
|
162
|
+
if (json) {
|
|
163
|
+
const obj = Object.fromEntries(Array.from(named.entries()).map(([name, key]) => [name, key]));
|
|
164
|
+
return printJson(obj);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
header("Named Plugins");
|
|
168
|
+
|
|
169
|
+
const entries = Array.from(named.entries());
|
|
170
|
+
if (entries.length === 0) {
|
|
171
|
+
console.log(" No named plugins.");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const rows = entries.map(([name, key]) => [
|
|
176
|
+
name,
|
|
177
|
+
key.plugin.toString(),
|
|
178
|
+
truncateAddress(key.caller),
|
|
179
|
+
key.escrow || "(default)",
|
|
180
|
+
]);
|
|
181
|
+
printColumns(["Name", "Plugin App", "Caller", "Escrow"], rows);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function walletEscrows(dao: AkitaDaoSDK, json: boolean): Promise<void> {
|
|
185
|
+
const wallet = await getWallet(dao);
|
|
186
|
+
const escrows = await wallet.getEscrows();
|
|
187
|
+
|
|
188
|
+
if (json) {
|
|
189
|
+
const obj = Object.fromEntries(escrows.entries());
|
|
190
|
+
return printJson(obj);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
header("Escrows");
|
|
194
|
+
|
|
195
|
+
const entries = Array.from(escrows.entries());
|
|
196
|
+
if (entries.length === 0) {
|
|
197
|
+
console.log(" No escrows.");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const rows = entries.map(([name, info]) => [
|
|
202
|
+
name,
|
|
203
|
+
info.id.toString(),
|
|
204
|
+
String(info.locked),
|
|
205
|
+
]);
|
|
206
|
+
printColumns(["Name", "App ID", "Locked"], rows);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export async function walletAllowances(dao: AkitaDaoSDK, json: boolean): Promise<void> {
|
|
210
|
+
const wallet = await getWallet(dao);
|
|
211
|
+
const allowances = await wallet.getAllowances();
|
|
212
|
+
|
|
213
|
+
const entries = Array.from(allowances.entries());
|
|
214
|
+
|
|
215
|
+
if (json) {
|
|
216
|
+
const arr = entries.map(([key, info]) => ({ key, ...info }));
|
|
217
|
+
return printJson(arr);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
header("Allowances");
|
|
221
|
+
|
|
222
|
+
if (entries.length === 0) {
|
|
223
|
+
console.log(" No allowances.");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const rows = entries.map(([key, info]) => {
|
|
228
|
+
let details: string;
|
|
229
|
+
if (info.type === "flat") {
|
|
230
|
+
details = `amount: ${formatBigInt(info.amount)}, spent: ${formatBigInt(info.spent)}`;
|
|
231
|
+
} else if (info.type === "window") {
|
|
232
|
+
details = `amount: ${formatBigInt(info.amount)}, spent: ${formatBigInt(info.spent)}, interval: ${formatDuration(info.interval)}`;
|
|
233
|
+
} else {
|
|
234
|
+
details = `rate: ${formatBigInt(info.rate)}, max: ${formatBigInt(info.max)}, interval: ${formatDuration(info.interval)}`;
|
|
235
|
+
}
|
|
236
|
+
return [key, info.type, details];
|
|
237
|
+
});
|
|
238
|
+
printColumns(["Key", "Type", "Details"], rows);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function walletExecutions(dao: AkitaDaoSDK, json: boolean): Promise<void> {
|
|
242
|
+
const wallet = await getWallet(dao);
|
|
243
|
+
const executions = await wallet.getExecutions();
|
|
244
|
+
|
|
245
|
+
if (json) {
|
|
246
|
+
const arr = Array.from(executions.entries()).map(([key, info]) => ({
|
|
247
|
+
key: Buffer.from(key).toString("hex"),
|
|
248
|
+
info,
|
|
249
|
+
}));
|
|
250
|
+
return printJson(arr);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
header("Execution Keys");
|
|
254
|
+
|
|
255
|
+
const entries = Array.from(executions.entries());
|
|
256
|
+
if (entries.length === 0) {
|
|
257
|
+
console.log(" No execution keys.");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const rows = entries.map(([key, info]) => [
|
|
262
|
+
Buffer.from(key).toString("hex").slice(0, 16) + "...",
|
|
263
|
+
info.firstValid.toString(),
|
|
264
|
+
info.lastValid.toString(),
|
|
265
|
+
]);
|
|
266
|
+
printColumns(["Lease (hex)", "First Valid", "Last Valid"], rows);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function walletBalance(
|
|
270
|
+
dao: AkitaDaoSDK,
|
|
271
|
+
network: AkitaNetwork,
|
|
272
|
+
json: boolean,
|
|
273
|
+
assetArgs: string[],
|
|
274
|
+
escrowName?: string
|
|
275
|
+
): Promise<void> {
|
|
276
|
+
const wallet = await getWallet(dao);
|
|
277
|
+
const ids = getNetworkAppIds(network);
|
|
278
|
+
|
|
279
|
+
// Determine which address to check
|
|
280
|
+
let address: string;
|
|
281
|
+
let label: string;
|
|
282
|
+
|
|
283
|
+
if (escrowName) {
|
|
284
|
+
const escrows = await wallet.getEscrows();
|
|
285
|
+
const escrow = escrows.get(escrowName);
|
|
286
|
+
if (!escrow) {
|
|
287
|
+
console.error(`Escrow "${escrowName}" not found. Use 'akita wallet escrows' to see available escrows.`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
address = algosdk.getApplicationAddress(escrow.id).toString();
|
|
291
|
+
label = `Escrow: ${escrowName}`;
|
|
292
|
+
} else {
|
|
293
|
+
address = algosdk.getApplicationAddress(wallet.appId).toString();
|
|
294
|
+
label = "Wallet";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
let assets: bigint[];
|
|
298
|
+
if (assetArgs.length > 0) {
|
|
299
|
+
assets = assetArgs.map((a) => BigInt(a));
|
|
300
|
+
} else {
|
|
301
|
+
// Default: ALGO (0), AKTA, BONES
|
|
302
|
+
assets = [0n, ids.akta, ids.bones];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const assetLabels = assets.map((a) => {
|
|
306
|
+
if (a === 0n) return "ALGO";
|
|
307
|
+
if (a === ids.akta) return "AKTA";
|
|
308
|
+
if (a === ids.bones) return "BONES";
|
|
309
|
+
if (a === ids.usdc) return "USDC";
|
|
310
|
+
return a.toString();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Fetch balances via algod account info
|
|
314
|
+
let balances: bigint[];
|
|
315
|
+
try {
|
|
316
|
+
const accountInfo = await dao.algorand.account.getInformation(address);
|
|
317
|
+
balances = assets.map((assetId) => {
|
|
318
|
+
if (assetId === 0n) {
|
|
319
|
+
return BigInt(accountInfo.amount);
|
|
320
|
+
}
|
|
321
|
+
const holding = accountInfo.assets?.find(
|
|
322
|
+
(a: { assetId: bigint }) => a.assetId === assetId
|
|
323
|
+
);
|
|
324
|
+
return holding ? holding.amount : 0n;
|
|
325
|
+
});
|
|
326
|
+
} catch (e: any) {
|
|
327
|
+
console.error(`Failed to fetch balances: ${e.message ?? e}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (json) {
|
|
332
|
+
const data = assets.map((asset, i) => ({
|
|
333
|
+
asset,
|
|
334
|
+
label: assetLabels[i],
|
|
335
|
+
balance: balances[i],
|
|
336
|
+
}));
|
|
337
|
+
return printJson(data);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
header(`${label} Balances`);
|
|
341
|
+
const rows = assets.map((asset, i) => {
|
|
342
|
+
let formatted: string;
|
|
343
|
+
if (asset === 0n) {
|
|
344
|
+
// ALGO: 6 decimals
|
|
345
|
+
const whole = balances[i] / 1_000_000n;
|
|
346
|
+
const frac = balances[i] % 1_000_000n;
|
|
347
|
+
formatted = frac === 0n
|
|
348
|
+
? whole.toLocaleString()
|
|
349
|
+
: `${whole.toLocaleString()}.${frac.toString().padStart(6, "0").replace(/0+$/, "")}`;
|
|
350
|
+
} else {
|
|
351
|
+
formatted = formatBigInt(balances[i]);
|
|
352
|
+
}
|
|
353
|
+
return [assetLabels[i], asset.toString(), formatted];
|
|
354
|
+
});
|
|
355
|
+
printColumns(["Asset", "ID", "Balance"], rows);
|
|
356
|
+
}
|