@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,659 @@
|
|
|
1
|
+
import type { AkitaNetwork, NetworkAppIds } from "@akta/sdk";
|
|
2
|
+
import { getNetworkAppIds } from "@akta/sdk";
|
|
3
|
+
import { ABIMethod, ABIType } from "algosdk";
|
|
4
|
+
import theme from "./theme";
|
|
5
|
+
|
|
6
|
+
const EMPTY_CID_STR = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
|
7
|
+
const ZERO_ADDRESS = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ";
|
|
8
|
+
|
|
9
|
+
export function truncateAddress(addr: string, chars: number = 6): string {
|
|
10
|
+
if (addr.length <= chars * 2 + 3) return addr;
|
|
11
|
+
return `${addr.slice(0, chars)}...${addr.slice(-chars)}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function formatTimestamp(ts: bigint): string {
|
|
15
|
+
if (ts === 0n) return "-";
|
|
16
|
+
return new Date(Number(ts) * 1000).toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatCID(cid: Uint8Array | string): string {
|
|
20
|
+
const str = typeof cid === "string" ? cid : Buffer.from(cid).toString();
|
|
21
|
+
return str === EMPTY_CID_STR ? "(none)" : str;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function proposalStatusLabel(status: number): string {
|
|
25
|
+
switch (status) {
|
|
26
|
+
case 0: return "Draft";
|
|
27
|
+
case 10: return "Invalid";
|
|
28
|
+
case 20: return "Voting";
|
|
29
|
+
case 30: return "Rejected";
|
|
30
|
+
case 40: return "Approved";
|
|
31
|
+
case 50: return "Executed";
|
|
32
|
+
default: return `Unknown(${status})`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function formatMicroAlgo(microAlgos: bigint): string {
|
|
37
|
+
const whole = microAlgos / 1_000_000n;
|
|
38
|
+
const frac = microAlgos % 1_000_000n;
|
|
39
|
+
if (frac === 0n) return `${whole} ALGO`;
|
|
40
|
+
return `${whole}.${frac.toString().padStart(6, "0").replace(/0+$/, "")} ALGO`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function proposalActionLabel(type: number): string {
|
|
44
|
+
switch (type) {
|
|
45
|
+
case 10: return "Upgrade App";
|
|
46
|
+
case 20: return "Add Plugin";
|
|
47
|
+
case 21: return "Add Named Plugin";
|
|
48
|
+
case 30: return "Execute Plugin";
|
|
49
|
+
case 31: return "Remove Execute Plugin";
|
|
50
|
+
case 40: return "Remove Plugin";
|
|
51
|
+
case 41: return "Remove Named Plugin";
|
|
52
|
+
case 50: return "Add Allowances";
|
|
53
|
+
case 60: return "Remove Allowances";
|
|
54
|
+
case 70: return "New Escrow";
|
|
55
|
+
case 71: return "Toggle Escrow Lock";
|
|
56
|
+
case 80: return "Update Fields";
|
|
57
|
+
default: return `Unknown(${type})`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatBigInt(val: bigint): string {
|
|
62
|
+
return val.toLocaleString();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Compact number: 1K, 1.5M, 2.3B, etc. with colored suffixes.
|
|
66
|
+
* Pass `decimals` to convert from smallest unit (e.g. 6 for microAlgos). */
|
|
67
|
+
export function formatCompact(val: bigint, decimals = 0): string {
|
|
68
|
+
const n = decimals > 0 ? Number(val) / 10 ** decimals : Number(val);
|
|
69
|
+
|
|
70
|
+
if (n < 1_000) {
|
|
71
|
+
if (Number.isInteger(n)) return n.toString();
|
|
72
|
+
if (n < 1) return n.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
73
|
+
if (n < 10) return n.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
|
|
74
|
+
if (n < 100) return n.toFixed(1).replace(/0+$/, "").replace(/\.$/, "");
|
|
75
|
+
return Math.round(n).toString();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const tiers: [number, string, (s: string) => string][] = [
|
|
79
|
+
[1e3, "K", theme.suffixK],
|
|
80
|
+
[1e6, "M", theme.suffixM],
|
|
81
|
+
[1e9, "B", theme.suffixB],
|
|
82
|
+
[1e12, "T", theme.suffixT],
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// Walk up tiers; promote if rounding would produce "1000X"
|
|
86
|
+
for (let i = 0; i < tiers.length; i++) {
|
|
87
|
+
const scaled = n / tiers[i][0];
|
|
88
|
+
if (scaled < 1000 || i === tiers.length - 1) {
|
|
89
|
+
const rounded = roundForTier(scaled);
|
|
90
|
+
if (rounded >= 1000 && i < tiers.length - 1) continue; // promote
|
|
91
|
+
return trimSuffix(scaled, tiers[i][1], tiers[i][2]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return n.toString();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function roundForTier(n: number): number {
|
|
98
|
+
if (n >= 100) return Math.round(n);
|
|
99
|
+
if (n >= 10) return Math.round(n * 10) / 10;
|
|
100
|
+
return Math.round(n * 100) / 100;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function trimSuffix(n: number, suffix: string, color: (s: string) => string): string {
|
|
104
|
+
let numStr: string;
|
|
105
|
+
if (n >= 100) numStr = `${Math.round(n)}`;
|
|
106
|
+
else if (n >= 10) numStr = `${Math.round(n * 10) / 10}`.replace(/\.0$/, "");
|
|
107
|
+
else numStr = `${Math.round(n * 100) / 100}`.replace(/\.00$/, "").replace(/(\.\d)0$/, "$1");
|
|
108
|
+
return numStr + color(suffix);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Format basis points (1000 = 1%) as percentage string */
|
|
112
|
+
export function formatBasisPoints(bp: bigint): string {
|
|
113
|
+
const pct = Number(bp) / 1000;
|
|
114
|
+
if (pct === Math.floor(pct)) return `${pct}%`;
|
|
115
|
+
return `${pct}%`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Format duration in seconds to human-readable string */
|
|
119
|
+
export function formatDuration(seconds: bigint): string {
|
|
120
|
+
const s = Number(seconds);
|
|
121
|
+
if (s === 0) return "0s";
|
|
122
|
+
|
|
123
|
+
const days = Math.floor(s / 86400);
|
|
124
|
+
const hours = Math.floor((s % 86400) / 3600);
|
|
125
|
+
const mins = Math.floor((s % 3600) / 60);
|
|
126
|
+
const secs = s % 60;
|
|
127
|
+
|
|
128
|
+
const parts: string[] = [];
|
|
129
|
+
if (days > 0) parts.push(`${days}d`);
|
|
130
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
131
|
+
if (mins > 0) parts.push(`${mins}m`);
|
|
132
|
+
if (secs > 0 && days === 0) parts.push(`${secs}s`);
|
|
133
|
+
|
|
134
|
+
return parts.join(" ");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** DAO state enum (0=Inactive, 1=Active, etc.) */
|
|
138
|
+
export function daoStateLabel(state: number): string {
|
|
139
|
+
switch (state) {
|
|
140
|
+
case 0: return `Inactive (${state})`;
|
|
141
|
+
case 1: return `Active (${state})`;
|
|
142
|
+
case 2: return `Paused (${state})`;
|
|
143
|
+
default: return `Unknown (${state})`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Delegation type enum */
|
|
148
|
+
export function delegationTypeLabel(type: bigint | number): string {
|
|
149
|
+
const n = Number(type);
|
|
150
|
+
switch (n) {
|
|
151
|
+
case 0: return `None (${n})`;
|
|
152
|
+
case 1: return `Caller (${n})`;
|
|
153
|
+
case 2: return `Anyone (${n})`;
|
|
154
|
+
default: return `Unknown (${n})`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Check if address is the zero address */
|
|
159
|
+
export function isZeroAddress(addr: string): boolean {
|
|
160
|
+
return addr === ZERO_ADDRESS;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Camel case key to readable label */
|
|
164
|
+
export function camelToLabel(key: string): string {
|
|
165
|
+
return key
|
|
166
|
+
.replace(/([A-Z])/g, " $1")
|
|
167
|
+
.replace(/^./, (c) => c.toUpperCase())
|
|
168
|
+
.trim();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// --- App ID reverse lookup ---
|
|
172
|
+
|
|
173
|
+
let _appNameCache: Map<string, Map<bigint, string>> | null = null;
|
|
174
|
+
|
|
175
|
+
function getAppNameMap(network: AkitaNetwork): Map<bigint, string> {
|
|
176
|
+
if (!_appNameCache) _appNameCache = new Map();
|
|
177
|
+
if (_appNameCache.has(network)) return _appNameCache.get(network)!;
|
|
178
|
+
|
|
179
|
+
const ids = getNetworkAppIds(network);
|
|
180
|
+
const map = new Map<bigint, string>();
|
|
181
|
+
|
|
182
|
+
// Human-readable names for known app IDs
|
|
183
|
+
const labels: Record<keyof NetworkAppIds, string> = {
|
|
184
|
+
dao: "DAO",
|
|
185
|
+
wallet: "Wallet",
|
|
186
|
+
escrowFactory: "Escrow Factory",
|
|
187
|
+
walletFactory: "Wallet Factory",
|
|
188
|
+
subscriptions: "Subscriptions",
|
|
189
|
+
stakingPoolFactory: "Staking Pool Factory",
|
|
190
|
+
staking: "Staking",
|
|
191
|
+
rewards: "Rewards",
|
|
192
|
+
social: "Social",
|
|
193
|
+
socialGraph: "Social Graph",
|
|
194
|
+
socialImpact: "Social Impact",
|
|
195
|
+
socialModeration: "Social Moderation",
|
|
196
|
+
auctionFactory: "Auction Factory",
|
|
197
|
+
marketplace: "Marketplace",
|
|
198
|
+
raffleFactory: "Raffle Factory",
|
|
199
|
+
pollFactory: "Poll Factory",
|
|
200
|
+
prizeBoxFactory: "Prize Box Factory",
|
|
201
|
+
revenueManagerPlugin: "Revenue Manager Plugin",
|
|
202
|
+
updatePlugin: "Update Plugin",
|
|
203
|
+
optinPlugin: "Opt-In Plugin",
|
|
204
|
+
asaMintPlugin: "ASA Mint Plugin",
|
|
205
|
+
payPlugin: "Pay Plugin",
|
|
206
|
+
hyperSwapPlugin: "Hyper Swap Plugin",
|
|
207
|
+
subscriptionsPlugin: "Subscriptions Plugin",
|
|
208
|
+
auctionPlugin: "Auction Plugin",
|
|
209
|
+
daoPlugin: "DAO Plugin",
|
|
210
|
+
dualStakePlugin: "Dual Stake Plugin",
|
|
211
|
+
gatePlugin: "Gate Plugin",
|
|
212
|
+
marketplacePlugin: "Marketplace Plugin",
|
|
213
|
+
nfdPlugin: "NFD Plugin",
|
|
214
|
+
paySiloPlugin: "Pay Silo Plugin",
|
|
215
|
+
paySiloFactoryPlugin: "Pay Silo Factory Plugin",
|
|
216
|
+
pollPlugin: "Poll Plugin",
|
|
217
|
+
rafflePlugin: "Raffle Plugin",
|
|
218
|
+
rewardsPlugin: "Rewards Plugin",
|
|
219
|
+
socialPlugin: "Social Plugin",
|
|
220
|
+
stakingPlugin: "Staking Plugin",
|
|
221
|
+
stakingPoolPlugin: "Staking Pool Plugin",
|
|
222
|
+
gate: "Gate",
|
|
223
|
+
hyperSwap: "Hyper Swap",
|
|
224
|
+
metaMerkles: "Meta Merkles",
|
|
225
|
+
akitaReferrerGate: "Akita Referrer Gate",
|
|
226
|
+
assetGate: "Asset Gate",
|
|
227
|
+
merkleAddressGate: "Merkle Address Gate",
|
|
228
|
+
merkleAssetGate: "Merkle Asset Gate",
|
|
229
|
+
nfdGate: "NFD Gate",
|
|
230
|
+
nfdRootGate: "NFD Root Gate",
|
|
231
|
+
pollGate: "Poll Gate",
|
|
232
|
+
socialActivityGate: "Social Activity Gate",
|
|
233
|
+
socialFollowerCountGate: "Social Follower Count Gate",
|
|
234
|
+
socialFollowerIndexGate: "Social Follower Index Gate",
|
|
235
|
+
socialImpactGate: "Social Impact Gate",
|
|
236
|
+
socialModeratorGate: "Social Moderator Gate",
|
|
237
|
+
stakingAmountGate: "Staking Amount Gate",
|
|
238
|
+
stakingPowerGate: "Staking Power Gate",
|
|
239
|
+
subscriptionGate: "Subscription Gate",
|
|
240
|
+
subscriptionStreakGate: "Subscription Streak Gate",
|
|
241
|
+
akta: "AKTA",
|
|
242
|
+
bones: "BONES",
|
|
243
|
+
usdc: "USDC",
|
|
244
|
+
vrfBeacon: "VRF Beacon",
|
|
245
|
+
nfdRegistry: "NFD Registry",
|
|
246
|
+
assetInbox: "Asset Inbox",
|
|
247
|
+
akitaNfd: "Akita NFD",
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
for (const [key, label] of Object.entries(labels)) {
|
|
251
|
+
const id = ids[key as keyof NetworkAppIds];
|
|
252
|
+
if (id > 0n) map.set(id, label);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_appNameCache.set(network, map);
|
|
256
|
+
return map;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Resolve an app ID to its human-readable name, or fall back to the raw ID */
|
|
260
|
+
export function resolveAppName(appId: bigint, network: AkitaNetwork): string {
|
|
261
|
+
const map = getAppNameMap(network);
|
|
262
|
+
const name = map.get(appId);
|
|
263
|
+
return name ? `${name} (${appId})` : appId.toString();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Resolve an app ID to just its short name, or undefined */
|
|
267
|
+
export function getAppName(appId: bigint, network: AkitaNetwork): string | undefined {
|
|
268
|
+
return getAppNameMap(network).get(appId);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Color helpers ────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
/** Color-code proposal status */
|
|
274
|
+
export function colorStatus(status: string): string {
|
|
275
|
+
switch (status) {
|
|
276
|
+
case "Approved":
|
|
277
|
+
case "Executed":
|
|
278
|
+
return theme.statusApproved(status);
|
|
279
|
+
case "Voting":
|
|
280
|
+
return theme.statusVoting(status);
|
|
281
|
+
case "Draft":
|
|
282
|
+
return theme.statusDraft(status);
|
|
283
|
+
case "Rejected":
|
|
284
|
+
case "Invalid":
|
|
285
|
+
return theme.statusRejected(status);
|
|
286
|
+
default:
|
|
287
|
+
return status;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Color-code DAO state */
|
|
292
|
+
export function colorState(state: string): string {
|
|
293
|
+
if (state.startsWith("Active")) return theme.stateActive(state);
|
|
294
|
+
if (state.startsWith("Inactive")) return theme.stateInactive(state);
|
|
295
|
+
if (state.startsWith("Paused")) return theme.statePaused(state);
|
|
296
|
+
return state;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Color-code boolean values */
|
|
300
|
+
export function colorBool(val: boolean): string {
|
|
301
|
+
return val ? theme.boolTrue(String(val)) : theme.boolFalse(String(val));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ── Plugin key parsing ──────────────────────────────────────────
|
|
305
|
+
|
|
306
|
+
export function parsePluginKey(key: string): { pluginId: bigint | null; caller: string; escrow: string } {
|
|
307
|
+
let digitEnd = 0;
|
|
308
|
+
while (digitEnd < key.length && key[digitEnd] >= "0" && key[digitEnd] <= "9") {
|
|
309
|
+
digitEnd++;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (digitEnd === 0) {
|
|
313
|
+
return { pluginId: null, caller: "", escrow: key };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const pluginId = BigInt(key.slice(0, digitEnd));
|
|
317
|
+
const rest = key.slice(digitEnd);
|
|
318
|
+
|
|
319
|
+
if (rest.length >= 58) {
|
|
320
|
+
return { pluginId, caller: rest.slice(0, 58), escrow: rest.slice(58) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { pluginId, caller: rest, escrow: "" };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ── Method selector decoding ────────────────────────────────────
|
|
327
|
+
|
|
328
|
+
/** All known ABI method signatures across the 21 SDK wallet plugins */
|
|
329
|
+
const PLUGIN_SIGNATURES: string[] = [
|
|
330
|
+
"accept(uint64,bool,uint64,address,byte[32][])void",
|
|
331
|
+
"add(uint64,bool,uint64,uint64,byte[][])void",
|
|
332
|
+
"addAction(uint64,bool,uint64,byte[36])void",
|
|
333
|
+
"addModerator(uint64,bool,address)void",
|
|
334
|
+
"addReward(uint64,bool,uint64,(uint64,uint8,uint64,uint64,uint64,uint64,uint64,uint64,uint64[],(uint64,uint64,uint64),uint64,uint8,uint64,uint64,uint64,uint64),uint64)void",
|
|
335
|
+
"ban(uint64,bool,address,uint64)void",
|
|
336
|
+
"bid(uint64,bool,uint64,uint64,byte[][],address)void",
|
|
337
|
+
"block(uint64,bool,address)void",
|
|
338
|
+
"cancel(uint64,bool,uint64)void",
|
|
339
|
+
"cancelSale(uint64,bool,uint64)void",
|
|
340
|
+
"changePrice(uint64,bool,uint64,uint64)void",
|
|
341
|
+
"checkTipMbrRequirements(uint64,address,uint64)(uint8,uint64)",
|
|
342
|
+
"claimPrize(uint64,bool,uint64)void",
|
|
343
|
+
"claimRafflePrize(uint64,bool,uint64)void",
|
|
344
|
+
"claimRewards(uint64,bool,(uint64,uint64)[])void",
|
|
345
|
+
"cleanupFill(uint64,bool,uint64,address,address,uint64,uint64,uint64,uint64)void",
|
|
346
|
+
"clearWeightsBoxes(uint64,bool,uint64,uint64)void",
|
|
347
|
+
"closeOutAlgo(uint64,bool,address,uint64,address)void",
|
|
348
|
+
"closeOutAsset(uint64,bool,uint64,address,address)void",
|
|
349
|
+
"contractLock(uint64,bool,uint64,bool)void",
|
|
350
|
+
"createApplication(string,uint64,uint64)void",
|
|
351
|
+
"createAsaUserAllocations(uint64,bool,uint64,uint64,(address,uint64)[],uint64)void",
|
|
352
|
+
"createDisbursement(uint64,bool,string,uint64,uint64,string)uint64",
|
|
353
|
+
"createHeartbeat(uint64,bool,address,uint64)void",
|
|
354
|
+
"createUserAllocations(uint64,bool,uint64,(address,uint64)[],uint64)void",
|
|
355
|
+
"deleteApplication(uint64,bool,uint64)void",
|
|
356
|
+
"deleteAuctionApp(uint64,bool,uint64)void",
|
|
357
|
+
"deleteBoxedContract(uint64,bool)void",
|
|
358
|
+
"deleteFields(uint64,bool,uint64,byte[][])void",
|
|
359
|
+
"deletePool(uint64,bool,uint64)void",
|
|
360
|
+
"deleteRaffleApplication(uint64,bool,uint64)void",
|
|
361
|
+
"deleteReaction(uint64,bool,byte[32],uint64)void",
|
|
362
|
+
"delist(uint64,bool,uint64)void",
|
|
363
|
+
"disburse(uint64,bool,uint64,address,uint64,address,uint64,uint64,uint64,uint64)void",
|
|
364
|
+
"editDisbursement(uint64,bool,uint64,string,uint64,uint64,string)void",
|
|
365
|
+
"editPost(uint64,bool,byte[36],byte[32])void",
|
|
366
|
+
"editProposal(uint64,bool,uint64,byte[36],(uint8,byte[])[])void",
|
|
367
|
+
"editReply(uint64,bool,byte[36],byte[32])void",
|
|
368
|
+
"editVote(uint64,bool,byte[32],bool)void",
|
|
369
|
+
"enter(uint64,bool,uint64,(uint64,uint64,byte[32][])[],byte[][])void",
|
|
370
|
+
"enter(uint64,bool,uint64,uint64,address,byte[][])void",
|
|
371
|
+
"escrow(uint64,bool,uint64,address,address,uint64,uint64,uint64,uint64,byte[32][])void",
|
|
372
|
+
"executeProposal(uint64,bool,uint64)void",
|
|
373
|
+
"fill(uint64,bool,uint64,address,address,uint64,uint64,uint64,uint64,byte[32][])void",
|
|
374
|
+
"finalizeDisbursement(uint64,bool,uint64)void",
|
|
375
|
+
"finalizeEscrowDisbursement(uint64,bool,uint64[])void",
|
|
376
|
+
"finalizePool(uint64,bool,uint64,uint64,uint64,uint64)void",
|
|
377
|
+
"finalizeProposal(uint64,bool,uint64)void",
|
|
378
|
+
"findWinner(uint64,bool,uint64,uint64)void",
|
|
379
|
+
"flagPost(uint64,bool,byte[32])void",
|
|
380
|
+
"follow(uint64,bool,address)void",
|
|
381
|
+
"gatedEditReply(uint64,bool,byte[36],byte[32],byte[][])void",
|
|
382
|
+
"gatedFill(uint64,bool,uint64,address,address,uint64,uint64,uint64,uint64,byte[32][],byte[][])void",
|
|
383
|
+
"gatedFollow(uint64,bool,address,byte[][])void",
|
|
384
|
+
"gatedReact(uint64,bool,byte[],uint8,uint64,byte[][])void",
|
|
385
|
+
"gatedReply(uint64,bool,uint64,byte[24],byte[36],byte[],uint8,uint64,byte[][],bool,uint64)void",
|
|
386
|
+
"initBoxedContract(uint64,string,uint64)void",
|
|
387
|
+
"initDescription(uint64,uint64)void",
|
|
388
|
+
"initMeta(uint64,bool,address,bool,uint64,uint64,uint64)uint64",
|
|
389
|
+
"initPool(uint64,bool,uint64)void",
|
|
390
|
+
"list(uint64,bool,uint64,uint64,uint64,uint64,uint64,address,uint64,address,string,byte[32][])uint64",
|
|
391
|
+
"loadBoxedContract(uint64,uint64,byte[])void",
|
|
392
|
+
"loadDescription(uint64,uint64,byte[])void",
|
|
393
|
+
"mint(uint64,bool,(string,string,uint64,uint64,address,address,address,address,bool,string)[],pay)uint64[]",
|
|
394
|
+
"mint(uint64,bool,address)uint64",
|
|
395
|
+
"mint(uint64,bool,uint64,uint64)void",
|
|
396
|
+
"new(uint64,bool,uint64,uint64,string,byte[32][],uint64,uint64,uint64,uint64,uint64,uint64,uint64,address,uint64)uint64",
|
|
397
|
+
"newPool(uint64,bool,string,uint8,address,(address,string),uint64,bool,uint64,uint64)void",
|
|
398
|
+
"newPrizeBoxRaffle(uint64,bool,uint64,uint64,uint64,uint64,uint64,uint64,uint64,address,uint64)uint64",
|
|
399
|
+
"newProposal(uint64,bool,byte[36],(uint8,byte[])[])uint64",
|
|
400
|
+
"newRaffle(uint64,bool,uint64,uint64,uint64,uint64,uint64,uint64,uint64,uint64,address,string,byte[32][],uint64)uint64",
|
|
401
|
+
"newReceiveEscrow(uint64,bool,string,address,bool,bool,((uint64,string),uint8,uint64)[])void",
|
|
402
|
+
"newReceiveEscrowWithRef(uint64,bool,string,address,bool,bool,(uint64,byte[]))void",
|
|
403
|
+
"newService(uint64,bool,uint64,uint64,uint64,uint64,uint64,string,byte[36],uint8,byte[3])uint64",
|
|
404
|
+
"offer(uint64,bool,byte[32],uint64,byte[32],uint64,uint64)void",
|
|
405
|
+
"offerForSale(uint64,bool,uint64,uint64,address)void",
|
|
406
|
+
"optIn(uint64,bool,uint64)void",
|
|
407
|
+
"optIn(uint64,bool,uint64[],pay)void",
|
|
408
|
+
"pauseService(uint64,bool,uint64)void",
|
|
409
|
+
"pay(uint64,bool,(address,uint64,uint64)[])void",
|
|
410
|
+
"pay(uint64,bool,(uint64,uint64)[])void",
|
|
411
|
+
"post(uint64,bool,uint64,byte[24],byte[36],uint64,bool,uint64)void",
|
|
412
|
+
"postOffer(uint64,bool,uint64,uint64,string)void",
|
|
413
|
+
"processEscrowAllocation(uint64,bool,uint64[])void",
|
|
414
|
+
"proxyPay(uint64,bool,uint64,address,uint64,uint64)void",
|
|
415
|
+
"purchase(uint64,bool,uint64,address,byte[][])void",
|
|
416
|
+
"purchase(uint64,bool,uint64)void",
|
|
417
|
+
"raffle(uint64,bool,uint64)void",
|
|
418
|
+
"react(uint64,bool,byte[],uint8,uint64)void",
|
|
419
|
+
"reclaimRewards(uint64,bool,uint64,(address,uint64)[])void",
|
|
420
|
+
"redeem(uint64,bool,uint64)void",
|
|
421
|
+
"refundBid(uint64,bool,uint64,uint64)void",
|
|
422
|
+
"register(uint64,bool,(uint64,uint64,uint8)[],byte[][])void",
|
|
423
|
+
"removeAction(uint64,bool,uint64)void",
|
|
424
|
+
"removeModerator(uint64,bool,address)void",
|
|
425
|
+
"renew(uint64,bool,uint64,uint64)void",
|
|
426
|
+
"reply(uint64,bool,uint64,byte[24],byte[36],byte[],uint8,uint64,bool,uint64)void",
|
|
427
|
+
"segmentLock(uint64,bool,uint64,bool,uint64)void",
|
|
428
|
+
"setClearProgram(uint64,bool,byte[])void",
|
|
429
|
+
"setPasses(uint64,bool,uint64,address[])void",
|
|
430
|
+
"setPrimaryAddress(uint64,bool,uint64,string,address)void",
|
|
431
|
+
"setup(uint64,bool,string)void",
|
|
432
|
+
"shutdownService(uint64,bool,uint64)void",
|
|
433
|
+
"softCheck(uint64,bool,address,uint64)void",
|
|
434
|
+
"stake(uint64,bool,uint64,uint8,uint64,uint64,bool)void",
|
|
435
|
+
"startEscrowDisbursement(uint64,bool)void",
|
|
436
|
+
"streakCheck(uint64,bool,(address,uint64))void",
|
|
437
|
+
"submitProposal(uint64,bool,uint64)void",
|
|
438
|
+
"subscribe(uint64,bool,uint64,address,uint64,uint64,uint64,byte[][])void",
|
|
439
|
+
"triggerPayment(uint64,bool,address,uint64,byte[][])void",
|
|
440
|
+
"unban(uint64,bool,address)void",
|
|
441
|
+
"unblock(uint64,bool,address)void",
|
|
442
|
+
"unflagPost(uint64,bool,byte[32])void",
|
|
443
|
+
"unfollow(uint64,bool,address)void",
|
|
444
|
+
"updateAkitaDAO(uint64)void",
|
|
445
|
+
"updateAkitaDaoAppIDForApp(uint64,bool,uint64,uint64)void",
|
|
446
|
+
"updateAkitaDaoEscrowForApp(uint64,bool,uint64,uint64)void",
|
|
447
|
+
"updateApp(uint64,bool,uint64)void",
|
|
448
|
+
"updateFactoryChildContract(uint64,bool,uint64)void",
|
|
449
|
+
"updateFields(uint64,bool,uint64,byte[][])void",
|
|
450
|
+
"updateHash(uint64,bool,uint64,byte[])void",
|
|
451
|
+
"updateMeta(uint64,bool,uint64,uint64,uint64,uint64,uint64,uint64)void",
|
|
452
|
+
"updateRevocation(uint64,bool,uint64)void",
|
|
453
|
+
"vaultOptIn(uint64,bool,uint64,uint64[])void",
|
|
454
|
+
"vaultOptInLock(uint64,bool,uint64,bool)void",
|
|
455
|
+
"vaultSend(uint64,bool,uint64,uint64,address,string,uint64,uint64[])void",
|
|
456
|
+
"vote(uint64,bool,byte[],uint8,bool)void",
|
|
457
|
+
"voteProposal(uint64,bool,uint64,uint8)void",
|
|
458
|
+
"withdraw(uint64,bool,uint64,address,address,uint64,uint64,uint64,uint64,byte[32][])void",
|
|
459
|
+
"withdraw(uint64,bool,uint64,uint8)void",
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
let _selectorMap: Map<string, string> | null = null;
|
|
463
|
+
|
|
464
|
+
function getSelectorMap(): Map<string, string> {
|
|
465
|
+
if (_selectorMap) return _selectorMap;
|
|
466
|
+
_selectorMap = new Map();
|
|
467
|
+
for (const sig of PLUGIN_SIGNATURES) {
|
|
468
|
+
try {
|
|
469
|
+
const method = ABIMethod.fromSignature(sig);
|
|
470
|
+
const hex = Buffer.from(method.getSelector()).toString("hex");
|
|
471
|
+
// First entry wins (preserves canonical name for duplicate selectors)
|
|
472
|
+
if (!_selectorMap.has(hex)) {
|
|
473
|
+
_selectorMap.set(hex, method.name);
|
|
474
|
+
}
|
|
475
|
+
} catch {
|
|
476
|
+
// Skip invalid signatures
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return _selectorMap;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Resolve a 4-byte method selector to a human-readable name, or hex fallback */
|
|
483
|
+
export function resolveMethodSelector(selector: Uint8Array): string {
|
|
484
|
+
const hex = Buffer.from(selector).toString("hex");
|
|
485
|
+
const name = getSelectorMap().get(hex);
|
|
486
|
+
return name ?? hex;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ── UpdateFields decoding ────────────────────────────────────────
|
|
490
|
+
|
|
491
|
+
type FieldFmt = "raw" | "appId" | "microAlgo" | "basisPoints" | "duration";
|
|
492
|
+
|
|
493
|
+
interface FieldDef {
|
|
494
|
+
name: string;
|
|
495
|
+
fmt: FieldFmt;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const PS_FIELDS: FieldDef[] = [
|
|
499
|
+
{ name: "Fee", fmt: "microAlgo" },
|
|
500
|
+
{ name: "Power", fmt: "raw" },
|
|
501
|
+
{ name: "Duration", fmt: "duration" },
|
|
502
|
+
{ name: "Participation", fmt: "basisPoints" },
|
|
503
|
+
{ name: "Approval", fmt: "basisPoints" },
|
|
504
|
+
];
|
|
505
|
+
|
|
506
|
+
const FIELD_SCHEMAS: Record<string, { label: string; fields: FieldDef[] }> = {
|
|
507
|
+
proposal_action_limit: { label: "Proposal Action Limit", fields: [{ name: "Limit", fmt: "raw" }] },
|
|
508
|
+
min_rewards_impact: { label: "Min Rewards Impact", fields: [{ name: "Impact", fmt: "raw" }] },
|
|
509
|
+
|
|
510
|
+
aal: { label: "Akita App List", fields: [
|
|
511
|
+
{ name: "Staking", fmt: "appId" }, { name: "Rewards", fmt: "appId" },
|
|
512
|
+
{ name: "Pool", fmt: "appId" }, { name: "Prize Box", fmt: "appId" },
|
|
513
|
+
{ name: "Subscriptions", fmt: "appId" }, { name: "Gate", fmt: "appId" },
|
|
514
|
+
{ name: "Auction", fmt: "appId" }, { name: "Hyper Swap", fmt: "appId" },
|
|
515
|
+
{ name: "Raffle", fmt: "appId" }, { name: "Meta Merkles", fmt: "appId" },
|
|
516
|
+
{ name: "Marketplace", fmt: "appId" }, { name: "Wallet", fmt: "appId" },
|
|
517
|
+
]},
|
|
518
|
+
sal: { label: "Social App List", fields: [
|
|
519
|
+
{ name: "Social", fmt: "appId" }, { name: "Graph", fmt: "appId" },
|
|
520
|
+
{ name: "Impact", fmt: "appId" }, { name: "Moderation", fmt: "appId" },
|
|
521
|
+
]},
|
|
522
|
+
pal: { label: "Plugin App List", fields: [
|
|
523
|
+
{ name: "Opt-In", fmt: "appId" }, { name: "Revenue Manager", fmt: "appId" },
|
|
524
|
+
{ name: "Update", fmt: "appId" },
|
|
525
|
+
]},
|
|
526
|
+
oal: { label: "Other App List", fields: [
|
|
527
|
+
{ name: "VRF Beacon", fmt: "appId" }, { name: "NFD Registry", fmt: "appId" },
|
|
528
|
+
{ name: "Asset Inbox", fmt: "appId" }, { name: "Escrow", fmt: "appId" },
|
|
529
|
+
{ name: "Poll", fmt: "appId" }, { name: "Akita NFD", fmt: "appId" },
|
|
530
|
+
]},
|
|
531
|
+
|
|
532
|
+
wallet_fees: { label: "Wallet Fees", fields: [
|
|
533
|
+
{ name: "Create Fee", fmt: "microAlgo" }, { name: "Referrer %", fmt: "basisPoints" },
|
|
534
|
+
]},
|
|
535
|
+
social_fees: { label: "Social Fees", fields: [
|
|
536
|
+
{ name: "Post Fee", fmt: "microAlgo" }, { name: "React Fee", fmt: "microAlgo" },
|
|
537
|
+
{ name: "Impact Tax Min", fmt: "basisPoints" }, { name: "Impact Tax Max", fmt: "basisPoints" },
|
|
538
|
+
]},
|
|
539
|
+
staking_fees: { label: "Staking Fees", fields: [
|
|
540
|
+
{ name: "Creation Fee", fmt: "microAlgo" },
|
|
541
|
+
{ name: "Impact Tax Min", fmt: "basisPoints" }, { name: "Impact Tax Max", fmt: "basisPoints" },
|
|
542
|
+
]},
|
|
543
|
+
subscription_fees: { label: "Subscription Fees", fields: [
|
|
544
|
+
{ name: "Service Creation Fee", fmt: "microAlgo" },
|
|
545
|
+
{ name: "Payment %", fmt: "basisPoints" }, { name: "Trigger %", fmt: "basisPoints" },
|
|
546
|
+
]},
|
|
547
|
+
nft_fees: { label: "NFT Fees", fields: [
|
|
548
|
+
{ name: "MP Sale % Min", fmt: "basisPoints" }, { name: "MP Sale % Max", fmt: "basisPoints" },
|
|
549
|
+
{ name: "MP Composable %", fmt: "basisPoints" }, { name: "MP Royalty Default %", fmt: "basisPoints" },
|
|
550
|
+
{ name: "Shuffle Sale %", fmt: "basisPoints" }, { name: "Omnigem Sale Fee", fmt: "microAlgo" },
|
|
551
|
+
{ name: "Auction Creation Fee", fmt: "microAlgo" },
|
|
552
|
+
{ name: "Auction Sale Tax Min", fmt: "basisPoints" }, { name: "Auction Sale Tax Max", fmt: "basisPoints" },
|
|
553
|
+
{ name: "Auction Composable %", fmt: "basisPoints" }, { name: "Auction Raffle %", fmt: "basisPoints" },
|
|
554
|
+
{ name: "Raffle Creation Fee", fmt: "microAlgo" },
|
|
555
|
+
{ name: "Raffle Sale Tax Min", fmt: "basisPoints" }, { name: "Raffle Sale Tax Max", fmt: "basisPoints" },
|
|
556
|
+
{ name: "Raffle Composable %", fmt: "basisPoints" },
|
|
557
|
+
]},
|
|
558
|
+
swap_fees: { label: "Swap Fees", fields: [
|
|
559
|
+
{ name: "Impact Tax Min", fmt: "basisPoints" }, { name: "Impact Tax Max", fmt: "basisPoints" },
|
|
560
|
+
]},
|
|
561
|
+
|
|
562
|
+
akita_assets: { label: "Akita Assets", fields: [
|
|
563
|
+
{ name: "AKTA", fmt: "raw" }, { name: "BONES", fmt: "raw" },
|
|
564
|
+
]},
|
|
565
|
+
|
|
566
|
+
upgrade_app_ps: { label: "Upgrade App Proposal Settings", fields: PS_FIELDS },
|
|
567
|
+
add_plugin_ps: { label: "Add Plugin Proposal Settings", fields: PS_FIELDS },
|
|
568
|
+
remove_plugin_ps: { label: "Remove Plugin Proposal Settings", fields: PS_FIELDS },
|
|
569
|
+
add_allowance_ps: { label: "Add Allowance Proposal Settings", fields: PS_FIELDS },
|
|
570
|
+
remove_allowance_ps: { label: "Remove Allowance Proposal Settings", fields: PS_FIELDS },
|
|
571
|
+
new_escrow_ps: { label: "New Escrow Proposal Settings", fields: PS_FIELDS },
|
|
572
|
+
update_fields_ps: { label: "Update Fields Proposal Settings", fields: PS_FIELDS },
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
function readUint64s(bytes: Uint8Array): bigint[] {
|
|
576
|
+
const result: bigint[] = [];
|
|
577
|
+
for (let i = 0; i + 8 <= bytes.length; i += 8) {
|
|
578
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + i, 8);
|
|
579
|
+
result.push(view.getBigUint64(0));
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function formatFieldValue(val: bigint, fmt: FieldFmt, network: AkitaNetwork): string {
|
|
585
|
+
switch (fmt) {
|
|
586
|
+
case "appId": return val === 0n ? "0" : resolveAppName(val, network);
|
|
587
|
+
case "microAlgo": return formatMicroAlgo(val);
|
|
588
|
+
case "basisPoints": return formatBasisPoints(val);
|
|
589
|
+
case "duration": return formatDuration(val);
|
|
590
|
+
default: return formatBigInt(val);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Decode an UpdateFields action value into KV pairs.
|
|
596
|
+
* Returns the field label and decoded pairs, or a raw hex fallback.
|
|
597
|
+
*/
|
|
598
|
+
export function decodeFieldUpdate(
|
|
599
|
+
field: string,
|
|
600
|
+
value: Uint8Array,
|
|
601
|
+
network: AkitaNetwork,
|
|
602
|
+
): { label: string; pairs: [string, string][] } {
|
|
603
|
+
// Content policy is a raw CID
|
|
604
|
+
if (field === "content_policy") {
|
|
605
|
+
return { label: "Content Policy", pairs: [["CID", formatCID(value)]] };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Revenue splits use ABI tuple encoding with variable-length strings
|
|
609
|
+
if (field === "revenue_splits") {
|
|
610
|
+
return decodeRevenueSplits(value, network);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const schema = FIELD_SCHEMAS[field];
|
|
614
|
+
if (!schema) {
|
|
615
|
+
const hex = Buffer.from(value).toString("hex");
|
|
616
|
+
return { label: field, pairs: [["Value", hex.length <= 32 ? hex : hex.slice(0, 32) + "..."]] };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const values = readUint64s(value);
|
|
620
|
+
const pairs: [string, string][] = [];
|
|
621
|
+
|
|
622
|
+
for (let i = 0; i < schema.fields.length && i < values.length; i++) {
|
|
623
|
+
const def = schema.fields[i];
|
|
624
|
+
pairs.push([def.name, formatFieldValue(values[i], def.fmt, network)]);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
return { label: schema.label, pairs };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function decodeRevenueSplits(
|
|
631
|
+
value: Uint8Array,
|
|
632
|
+
network: AkitaNetwork,
|
|
633
|
+
): { label: string; pairs: [string, string][] } {
|
|
634
|
+
try {
|
|
635
|
+
const abiType = ABIType.from("((uint64,string),uint8,uint64)[]");
|
|
636
|
+
const decoded = abiType.decode(value) as [[bigint, string], number, bigint][];
|
|
637
|
+
|
|
638
|
+
let pctSum = 0n;
|
|
639
|
+
for (const [, type, val] of decoded) {
|
|
640
|
+
if (type === 20) pctSum += val;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const pairs: [string, string][] = [];
|
|
644
|
+
for (const [[wallet, escrow], type, val] of decoded) {
|
|
645
|
+
const name = resolveAppName(wallet, network);
|
|
646
|
+
const esc = escrow || "(default)";
|
|
647
|
+
let share: string;
|
|
648
|
+
if (type === 20) share = formatBasisPoints(val);
|
|
649
|
+
else if (type === 30) share = formatBasisPoints(100_000n - pctSum);
|
|
650
|
+
else share = formatBigInt(val);
|
|
651
|
+
pairs.push([`${name} → ${esc}`, share]);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return { label: "Revenue Splits", pairs };
|
|
655
|
+
} catch {
|
|
656
|
+
const hex = Buffer.from(value).toString("hex");
|
|
657
|
+
return { label: "Revenue Splits", pairs: [["Value", hex.slice(0, 32) + "..."]] };
|
|
658
|
+
}
|
|
659
|
+
}
|