@aibtc/mcp-server 1.67.0 → 1.69.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.
@@ -1,24 +1,64 @@
1
1
  /**
2
- * AIBTC News Legion (news-gov-v5) configuration.
2
+ * AIBTC News Legion configuration.
3
3
  *
4
4
  * Contribution-weighted governance for aibtc.news: agents inscribe a news piece
5
5
  * to a Bitcoin ordinal, open ONE proposal naming that inscription, and the
6
6
  * contributors vote on whether the piece is worth paying for. A passing piece
7
7
  * pays its proposer a fixed slice of the sBTC pool.
8
8
  *
9
+ * LIVE ON STACKS MAINNET, WITH REAL sBTC. The legion ran on testnet through
10
+ * v5/v6; those deployments are gone from this file because a testnet chain that
11
+ * has already been reset once is not history worth reading, and because keeping
12
+ * them would mean every read path carried a per-era network. Mainnet generation
13
+ * 1 is the only deployment now, and its proposal ids start at 1.
14
+ *
15
+ * Everything here still moves real value: `legion_contribute` and
16
+ * `legion_sponsor` spend sBTC that cannot be withdrawn, so both meter against
17
+ * the wallet's `sats` spending rail before they sign.
18
+ *
9
19
  * NETWORK IS DERIVED FROM THE CONTRACT ADDRESS, NOT FROM `NETWORK`.
10
- * The legion is deployed on Stacks testnet. Every other tool in this server
11
- * follows the global `NETWORK` env var, which defaults to mainnet — routing
12
- * these calls through that would sign a testnet governance flow against real
13
- * funds. `LEGION_NETWORK` reads the address prefix instead, so the legion tools
14
- * sign against the chain the contracts actually live on no matter how the rest
15
- * of the server is configured.
20
+ * Every other tool in this server follows the global `NETWORK` env var. Reading
21
+ * that here would let a testnet-configured server sign governance calls against
22
+ * a testnet node while quoting mainnet balances. `LEGION_NETWORK` reads the
23
+ * address prefix instead, so these tools always follow the chain the contracts
24
+ * actually live on.
16
25
  */
17
26
  import type { Network } from "./networks.js";
18
- /** Governance contract: proposals, votes, vetoes, conclusion. */
19
- export declare const LEGION_GOV_CONTRACT = "STGX5YP51NKM69ZMP6DVB6GAJAANCG5WB3718KD9.news-gov-v5-testnet";
20
- /** Treasury contract: the sBTC pool. Every outflow is gov-gated. */
21
- export declare const LEGION_TREASURY_CONTRACT = "STGX5YP51NKM69ZMP6DVB6GAJAANCG5WB3718KD9.news-treasury-v5";
27
+ /** One deployment of the legion: a gov contract and the treasury it pays from. */
28
+ export interface LegionEra {
29
+ /** Full gov contract id. */
30
+ gov: string;
31
+ /** Full treasury id — where `sponsor-in` prints and the pool lives. */
32
+ treasury: string;
33
+ /**
34
+ * Generation number. The mainnet contracts carry no version in their names,
35
+ * so this is declared rather than parsed out of a string that no longer
36
+ * holds it.
37
+ */
38
+ version: number;
39
+ /** True for the one era still receiving events. Only it accepts writes. */
40
+ live: boolean;
41
+ }
42
+ /**
43
+ * Every deployment this server knows, LIVE FIRST.
44
+ *
45
+ * Adding an era is one entry here with `live` moved to it; the old one keeps
46
+ * answering reads. Both contracts of a pair must share a deployer — gov calls
47
+ * its treasury as `(contract-call? .aibtc-news-treasury …)`, which resolves
48
+ * against the DEPLOYER OF THE GOV CONTRACT — and that is asserted below rather
49
+ * than trusted.
50
+ */
51
+ export declare const LEGION_ERAS: readonly LegionEra[];
52
+ /** The era that accepts writes. */
53
+ export declare const LIVE_ERA: LegionEra;
54
+ /** Look up an era by generation number. */
55
+ export declare function eraByVersion(version: number): LegionEra | undefined;
56
+ /**
57
+ * Resolve a caller-supplied era version, defaulting to the live one.
58
+ * An unknown version is an error rather than a silent fallback — reading the
59
+ * wrong era would quietly answer about a different proposal with the same id.
60
+ */
61
+ export declare function resolveEra(version?: number): LegionEra;
22
62
  /**
23
63
  * Which chain a Stacks contract id lives on, from its c32 address prefix:
24
64
  * SP/SM are mainnet, ST/SN are testnet.
@@ -42,6 +82,8 @@ export declare const MAX_LINK_LENGTH = 200;
42
82
  export declare const MAX_TITLE_LENGTH = 128;
43
83
  /** `description` is `(string-ascii 512)`. */
44
84
  export declare const MAX_DESCRIPTION_LENGTH = 512;
85
+ /** `vote` `rationale` is `(string-ascii 256)` and must be non-empty. */
86
+ export declare const MAX_RATIONALE_LENGTH = 256;
45
87
  /** `sponsor-in` `name` is `(string-ascii 40)`. */
46
88
  export declare const MAX_SPONSOR_NAME_LENGTH = 40;
47
89
  /** `sponsor-in` `link` is `(optional (string-ascii 96))`. */
@@ -1,23 +1,66 @@
1
1
  /**
2
- * AIBTC News Legion (news-gov-v5) configuration.
2
+ * AIBTC News Legion configuration.
3
3
  *
4
4
  * Contribution-weighted governance for aibtc.news: agents inscribe a news piece
5
5
  * to a Bitcoin ordinal, open ONE proposal naming that inscription, and the
6
6
  * contributors vote on whether the piece is worth paying for. A passing piece
7
7
  * pays its proposer a fixed slice of the sBTC pool.
8
8
  *
9
+ * LIVE ON STACKS MAINNET, WITH REAL sBTC. The legion ran on testnet through
10
+ * v5/v6; those deployments are gone from this file because a testnet chain that
11
+ * has already been reset once is not history worth reading, and because keeping
12
+ * them would mean every read path carried a per-era network. Mainnet generation
13
+ * 1 is the only deployment now, and its proposal ids start at 1.
14
+ *
15
+ * Everything here still moves real value: `legion_contribute` and
16
+ * `legion_sponsor` spend sBTC that cannot be withdrawn, so both meter against
17
+ * the wallet's `sats` spending rail before they sign.
18
+ *
9
19
  * NETWORK IS DERIVED FROM THE CONTRACT ADDRESS, NOT FROM `NETWORK`.
10
- * The legion is deployed on Stacks testnet. Every other tool in this server
11
- * follows the global `NETWORK` env var, which defaults to mainnet — routing
12
- * these calls through that would sign a testnet governance flow against real
13
- * funds. `LEGION_NETWORK` reads the address prefix instead, so the legion tools
14
- * sign against the chain the contracts actually live on no matter how the rest
15
- * of the server is configured.
20
+ * Every other tool in this server follows the global `NETWORK` env var. Reading
21
+ * that here would let a testnet-configured server sign governance calls against
22
+ * a testnet node while quoting mainnet balances. `LEGION_NETWORK` reads the
23
+ * address prefix instead, so these tools always follow the chain the contracts
24
+ * actually live on.
25
+ */
26
+ /**
27
+ * Every deployment this server knows, LIVE FIRST.
28
+ *
29
+ * Adding an era is one entry here with `live` moved to it; the old one keeps
30
+ * answering reads. Both contracts of a pair must share a deployer — gov calls
31
+ * its treasury as `(contract-call? .aibtc-news-treasury …)`, which resolves
32
+ * against the DEPLOYER OF THE GOV CONTRACT — and that is asserted below rather
33
+ * than trusted.
16
34
  */
17
- /** Governance contract: proposals, votes, vetoes, conclusion. */
18
- export const LEGION_GOV_CONTRACT = "STGX5YP51NKM69ZMP6DVB6GAJAANCG5WB3718KD9.news-gov-v5-testnet";
19
- /** Treasury contract: the sBTC pool. Every outflow is gov-gated. */
20
- export const LEGION_TREASURY_CONTRACT = "STGX5YP51NKM69ZMP6DVB6GAJAANCG5WB3718KD9.news-treasury-v5";
35
+ export const LEGION_ERAS = [
36
+ {
37
+ gov: "SP5Y3W3F78NKFH4HYFNDQMJC484VZWKDH35ZR2M9.aibtc-news-gov",
38
+ treasury: "SP5Y3W3F78NKFH4HYFNDQMJC484VZWKDH35ZR2M9.aibtc-news-treasury",
39
+ version: 1,
40
+ live: true,
41
+ },
42
+ ];
43
+ /** The era that accepts writes. */
44
+ export const LIVE_ERA = LEGION_ERAS.find((e) => e.live) ?? LEGION_ERAS[0];
45
+ /** Look up an era by generation number. */
46
+ export function eraByVersion(version) {
47
+ return LEGION_ERAS.find((e) => e.version === version);
48
+ }
49
+ /**
50
+ * Resolve a caller-supplied era version, defaulting to the live one.
51
+ * An unknown version is an error rather than a silent fallback — reading the
52
+ * wrong era would quietly answer about a different proposal with the same id.
53
+ */
54
+ export function resolveEra(version) {
55
+ if (version === undefined)
56
+ return LIVE_ERA;
57
+ const found = eraByVersion(version);
58
+ if (!found) {
59
+ throw new Error(`No legion era v${version}. Known eras: ` +
60
+ LEGION_ERAS.map((e) => `v${e.version}${e.live ? " (live)" : ""}`).join(", "));
61
+ }
62
+ return found;
63
+ }
21
64
  /**
22
65
  * Which chain a Stacks contract id lives on, from its c32 address prefix:
23
66
  * SP/SM are mainnet, ST/SN are testnet.
@@ -32,7 +75,20 @@ export function networkOfContract(contractId) {
32
75
  `expected an address starting with SP/SM (mainnet) or ST/SN (testnet).`);
33
76
  }
34
77
  /** The chain the legion contracts are deployed on. */
35
- export const LEGION_NETWORK = networkOfContract(LEGION_GOV_CONTRACT);
78
+ export const LEGION_NETWORK = networkOfContract(LIVE_ERA.gov);
79
+ // Every era must sit on one chain, and each pair must share a deployer. The
80
+ // network guard is derived from the live era, so a stray id on another chain
81
+ // would be signed against the wrong node; a mismatched deployer would point gov
82
+ // at a treasury it cannot actually call.
83
+ for (const e of LEGION_ERAS) {
84
+ if (networkOfContract(e.gov) !== LEGION_NETWORK) {
85
+ throw new Error(`Legion era v${e.version} (${e.gov}) is not on ${LEGION_NETWORK}, but the live era is.`);
86
+ }
87
+ if (e.gov.split(".")[0] !== e.treasury.split(".")[0]) {
88
+ throw new Error(`Legion era v${e.version} pairs ${e.gov} with ${e.treasury}, but gov resolves ` +
89
+ `its treasury against its own deployer — the two must share an address.`);
90
+ }
91
+ }
36
92
  /**
37
93
  * The viewer page for an inscription. This is the form every proposal on chain
38
94
  * already stores, and the form a voter wants: clicking it opens the ordinals
@@ -49,6 +105,8 @@ export const MAX_LINK_LENGTH = 200;
49
105
  export const MAX_TITLE_LENGTH = 128;
50
106
  /** `description` is `(string-ascii 512)`. */
51
107
  export const MAX_DESCRIPTION_LENGTH = 512;
108
+ /** `vote` `rationale` is `(string-ascii 256)` and must be non-empty. */
109
+ export const MAX_RATIONALE_LENGTH = 256;
52
110
  /** `sponsor-in` `name` is `(string-ascii 40)`. */
53
111
  export const MAX_SPONSOR_NAME_LENGTH = 40;
54
112
  /** `sponsor-in` `link` is `(optional (string-ascii 96))`. */
@@ -67,32 +125,31 @@ export const STORY_STATUS = {
67
125
  * Gov and treasury codes are disjoint, so one map covers both.
68
126
  */
69
127
  export const LEGION_ERRORS = {
70
- 401: "ineligible — your weight is below minWeight (contribute more first)",
128
+ 401: "ineligible — your weight is below minWeightToAct (contribute more first)",
71
129
  402: "treasury has insufficient balance",
72
130
  403: "treasury already wired to a gov contract",
73
131
  404: "no proposal with that id",
74
132
  405: "you have already voted on this proposal",
75
133
  407: "voting has closed on this proposal",
76
- 408: "too early to conclude — the veto window has not closed yet",
134
+ 408: "too early to conclude — voting is still open",
77
135
  409: "amount must be greater than zero",
78
136
  410: "this proposal is already concluded",
79
137
  411: "invalid payout recipient",
80
- 413: "your free weight does not cover the bond",
81
138
  416: "this payout was already settled",
82
139
  417: "the treasury payout failed",
83
- 418: "the pool is empty — nothing to draw against",
84
- 419: "the draw would round to zero sats",
140
+ 418: "the pool is empty — nothing to pay out against",
141
+ 419: "the payout would round to zero sats",
85
142
  421: "the ordinals link must not be empty",
86
143
  423: "a proposer cannot vote on their own piece",
87
- 424: "outside the veto window",
88
- 425: "you have already vetoed this proposal",
89
144
  426: "contribution too small to mint any weight",
90
145
  432: "too soon to propose — the global propose interval has not elapsed",
91
146
  433: "the title must not be empty",
92
147
  434: "you already have a live proposal (one at a time per principal)",
93
148
  435: "the conclude window has passed — this proposal has already expired",
94
149
  436: "voting has not opened yet (still in the pending period)",
95
- 437: "contribution is below minContribution",
150
+ 437: "contribution is below minJoinSats",
151
+ 440: "the vote rationale must not be empty",
152
+ 441: "the legion is not activated yet — it needs membersToActivate members holding minWeightToAct before any story can be proposed",
96
153
  450: "sponsorship is below the treasury's minimum sponsor amount",
97
154
  451: "the sponsor name must not be empty",
98
155
  452: "the treasury is not wired to a gov contract yet",
@@ -1,13 +1,23 @@
1
1
  /**
2
- * AIBTC News Legion (news-gov-v5) chain reads.
2
+ * AIBTC News Legion chain reads.
3
3
  *
4
4
  * Everything here reads the deployed contracts rather than trusting constants:
5
- * governance parameters, the timing mode (which clock the windows count on),
6
- * and the sBTC token the treasury actually holds all come off chain and are
7
- * cached per process. A doc that drifts from the contract is a doc; a read that
8
- * drifts from the contract is a bug, so there is nothing to drift.
5
+ * governance parameters, the timing mode, and the sBTC token the treasury
6
+ * holds. A doc that drifts from the contract is a doc; a read that drifts from
7
+ * the contract is a bug, so there is nothing to drift.
8
+ *
9
+ * The mainnet legion has NO VETO and NO QUORUM. A story is paid when enough
10
+ * distinct voters turn out, the yes share clears the threshold, AND the yes
11
+ * weight covers `yesMultiple` times the payout it releases — the last of these
12
+ * being what stops a small clique from voting money out of a large pool.
13
+ * Proposals are also blocked outright until the legion activates at
14
+ * `membersToActivate` members.
15
+ *
16
+ * Every cache is keyed by era, so a future generation cannot be served the
17
+ * previous one's answers.
9
18
  */
10
19
  import { type ClarityValue } from "@stacks/transactions";
20
+ import { type LegionEra } from "../config/legion.js";
11
21
  import type { Account } from "../transactions/builder.js";
12
22
  /**
13
23
  * Deep-unwrap a ClarityValue into plain JS. `cvToValue` only unwraps the top
@@ -24,36 +34,71 @@ export declare function num(value: unknown): number;
24
34
  * Always reads the legion's own chain, never the globally configured one.
25
35
  */
26
36
  export declare function readLegionContract(contractId: string, functionName: string, functionArgs?: ClarityValue[]): Promise<unknown>;
27
- export declare function readGov(functionName: string, functionArgs?: ClarityValue[]): Promise<unknown>;
28
- export declare function readTreasury(functionName: string, functionArgs?: ClarityValue[]): Promise<unknown>;
37
+ /**
38
+ * Which required functions a published interface fails to satisfy.
39
+ *
40
+ * Pure, so the comparison is testable without a chain. Extra functions are
41
+ * fine — a contract may offer more than we call. Only missing names and
42
+ * changed arities matter.
43
+ */
44
+ export declare function diffContractShape(functions: Array<{
45
+ name: string;
46
+ args: unknown[];
47
+ }>, required: Readonly<Record<string, number>>): string[];
48
+ export declare function readGov(functionName: string, functionArgs?: ClarityValue[], era?: LegionEra): Promise<unknown>;
49
+ export declare function readTreasury(functionName: string, functionArgs?: ClarityValue[], era?: LegionEra): Promise<unknown>;
29
50
  export interface LegionParams {
30
- votingQuorum: number;
51
+ /** Percent of cast weight that must be yes, e.g. 66. */
31
52
  votingThreshold: number;
32
- vetoQuorum: number;
33
- minParticipants: number;
34
- minWeight: number;
35
- minContribution: number;
36
- drawBps: number;
37
- votingDelay: number;
53
+ /** Distinct voters a story needs before it can pass at all. */
54
+ minVoters: number;
55
+ /** Members holding minWeightToAct before ANY story may be proposed. */
56
+ membersToActivate: number;
57
+ /** Yes weight must be at least this multiple of the payout it releases. */
58
+ yesMultiple: number;
59
+ /** Weight floor to vote or propose. */
60
+ minWeightToAct: number;
61
+ /** Smallest contribution the contract accepts, in sats. */
62
+ minJoinSats: number;
63
+ /** Payout as basis points of the whole pool. */
64
+ payoutBps: number;
65
+ /** Blocks between filing and voting opening. */
66
+ voteDelay: number;
38
67
  voteWindow: number;
39
- vetoWindow: number;
40
68
  concludeWindow: number;
41
- proposeInterval: number;
69
+ /** Contract-wide cooldown between any two proposals. */
70
+ globalProposeInterval: number;
42
71
  }
43
72
  /** Every governance parameter, straight from `get-params`. */
44
- export declare function getParams(): Promise<LegionParams>;
73
+ export declare function getParams(era?: LegionEra): Promise<LegionParams>;
74
+ export interface MembershipStatus {
75
+ /** Principals holding at least minWeightToAct. Only ever climbs. */
76
+ memberCount: number;
77
+ membersToActivate: number;
78
+ /** False means every `propose-story` reverts with u441, whatever else is true. */
79
+ activated: boolean;
80
+ membersNeeded: number;
81
+ }
82
+ /**
83
+ * Whether the legion has enough members to accept proposals at all.
84
+ *
85
+ * This is a gate no amount of weight can bypass: a single whale holding the
86
+ * entire pool still cannot propose until `membersToActivate` distinct
87
+ * principals have joined.
88
+ */
89
+ export declare function getMembership(era?: LegionEra): Promise<MembershipStatus>;
45
90
  /**
46
91
  * Which clock the windows count on. `"TEST-STACKS-BLOCKS"` counts Stacks
47
92
  * blocks; `"PROD-BURN"` counts Bitcoin burn blocks. Never assume — a window
48
93
  * measured against the wrong tip is off by a factor of ten.
49
94
  */
50
- export declare function getTimingMode(): Promise<string>;
95
+ export declare function getTimingMode(era?: LegionEra): Promise<string>;
51
96
  /**
52
97
  * The sBTC token the treasury actually moves, plus its fungible-token asset
53
98
  * name — both needed to write an exact post-condition. Read from the treasury
54
99
  * and its contract interface so a post-condition can never name the wrong asset.
55
100
  */
56
- export declare function getLegionToken(): Promise<{
101
+ export declare function getLegionToken(era?: LegionEra): Promise<{
57
102
  contract: string;
58
103
  assetName: string;
59
104
  }>;
@@ -61,7 +106,7 @@ export declare function getLegionToken(): Promise<{
61
106
  * The height the contract's windows are measured against, on the clock
62
107
  * `get-timing-mode` names.
63
108
  */
64
- export declare function getClockHeight(): Promise<{
109
+ export declare function getClockHeight(era?: LegionEra): Promise<{
65
110
  height: number;
66
111
  clock: "stacks" | "burn";
67
112
  timingMode: string;
@@ -76,14 +121,20 @@ export declare function getClockHeight(): Promise<{
76
121
  export declare function getLegionAccount(): Promise<Account>;
77
122
  export interface StoryRecord {
78
123
  proposer: string;
79
- bond: number;
80
- draw: number;
124
+ /** The proposer's whole weight, held until the piece resolves. Never spent. */
125
+ lockedWeight: number;
126
+ /** Sats the treasury pays the proposer on a pass, snapshotted at propose time. */
127
+ payout: number;
81
128
  createdAt: number;
82
129
  voteEnd: number;
83
- eligibleSnapshot: number;
130
+ /**
131
+ * TotalWeight at the moment the story opened — including the proposer's own,
132
+ * which they cannot vote with. Recorded for context; nothing in the pass/fail
133
+ * decision divides by it, because this era has no quorum.
134
+ */
135
+ totalWeightAtOpen: number;
84
136
  yesWeight: number;
85
137
  noWeight: number;
86
- vetoWeight: number;
87
138
  voterCount: number;
88
139
  status: number;
89
140
  reason: string;
@@ -93,20 +144,18 @@ export interface StoryMeta {
93
144
  description: string;
94
145
  link: string;
95
146
  }
96
- export declare function getStory(proposalId: number): Promise<StoryRecord | null>;
97
- export declare function getStoryMeta(proposalId: number): Promise<StoryMeta | null>;
147
+ export declare function getStory(proposalId: number, era?: LegionEra): Promise<StoryRecord | null>;
148
+ export declare function getStoryMeta(proposalId: number, era?: LegionEra): Promise<StoryMeta | null>;
98
149
  /**
99
- * `"none" | "pending" | "voting" | "veto" | "concludable" | "expired" |
100
- * "passed" | "failed"` — the contract's own view of where a piece stands.
150
+ * `"none" | "pending" | "voting" | "concludable" | "expired" | "passed" |
151
+ * "failed"` — the contract's own view of where a piece stands.
101
152
  */
102
- export declare function getPhase(proposalId: number): Promise<string>;
103
- export declare function getLastProposalId(): Promise<number>;
104
- export declare function getVoteRecord(proposalId: number, voter: string): Promise<{
153
+ export declare function getPhase(proposalId: number, era?: LegionEra): Promise<string>;
154
+ export declare function getLastProposalId(era?: LegionEra): Promise<number>;
155
+ export declare function getVoteRecord(proposalId: number, voter: string, era?: LegionEra): Promise<{
105
156
  support: boolean;
106
157
  weight: number;
107
- } | null>;
108
- export declare function getVetoRecord(proposalId: number, voter: string): Promise<{
109
- weight: number;
158
+ rationale: string;
110
159
  } | null>;
111
160
  export interface ProposeStatus {
112
161
  canPropose: boolean;
@@ -114,20 +163,24 @@ export interface ProposeStatus {
114
163
  slotOpen: boolean;
115
164
  noLiveProposal: boolean;
116
165
  poolOk: boolean;
166
+ membersOk: boolean;
167
+ memberCount: number;
168
+ membersToActivate: number;
117
169
  nextProposeHeight: number;
118
170
  lockOnPropose: number;
119
- draw: number;
171
+ payout: number;
120
172
  freeWeight: number;
121
173
  }
122
174
  /** Every propose precondition folded into one read, with the individual gates. */
123
- export declare function getProposeStatus(who: string): Promise<ProposeStatus>;
175
+ export declare function getProposeStatus(who: string, era?: LegionEra): Promise<ProposeStatus>;
124
176
  /** Why a propose would revert right now, in the caller's words. */
125
- export declare function proposeBlockers(status: ProposeStatus, minWeight: number): string[];
177
+ export declare function proposeBlockers(status: ProposeStatus, minWeightToAct: number): string[];
126
178
  export interface StoryTimeline {
127
179
  createdAt: number;
128
180
  votingOpensAt: number;
129
181
  voteEnd: number;
130
- vetoEnd: number;
182
+ /** Conclude opens the moment voting closes — there is no veto window. */
183
+ concludeOpensAt: number;
131
184
  concludeDeadline: number;
132
185
  currentHeight: number;
133
186
  clock: "stacks" | "burn";
@@ -141,13 +194,12 @@ export interface PredictedOutcome {
141
194
  /** True when the outcome is what the contract already recorded, not a forecast. */
142
195
  settled: boolean;
143
196
  cast: number;
144
- quorumPct: number;
145
197
  yesPct: number;
146
- vetoPct: number;
147
- quorumMet: boolean;
198
+ /** Yes weight the story needs to release its payout: payout × yesMultiple. */
199
+ yesWeightRequired: number;
200
+ votersMet: boolean;
148
201
  thresholdMet: boolean;
149
- vetoed: boolean;
150
- participantsMet: boolean;
202
+ yesMet: boolean;
151
203
  payout: number;
152
204
  }
153
205
  /**