Skip to content

Commit 9b7535b

Browse files
committed
fix(dapp): curate outcome templates to registry and public goods
1 parent c48e877 commit 9b7535b

4 files changed

Lines changed: 80 additions & 157 deletions

File tree

dapp/src/components/page/proposal/OutcomeInput.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ const OutcomeInput = ({
175175
if (onModeChange) onModeChange("xdr");
176176
setXdr(template.xdr);
177177
if (onXdrChange) onXdrChange(template.xdr);
178+
} else {
179+
// Description-only templates represent outcomes with no
180+
// execution. Clear any previous execution mode/data so a
181+
// template cannot retain an older contract call or XDR.
182+
setMode("none");
183+
if (onModeChange) onModeChange("none");
184+
setContractOutcome(null);
185+
setXdr(null);
178186
}
179187
}}
180188
/>

dapp/src/components/page/proposal/OutcomeTemplateSelector.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export default function OutcomeTemplateSelector({
2323

2424
const templates = getOutcomeTemplatesByType(outcomeType);
2525

26+
if (templates.length === 0) return null;
27+
2628
const handleTemplateClick = useCallback(
2729
(template: OutcomeTemplate) => {
2830
onTemplateSelect(template);

dapp/src/constants/outcomeTemplates.ts

Lines changed: 36 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
*
1111
* Selecting a template pre-fills everything a user could otherwise fill out:
1212
* the description, and optionally the XDR transaction and/or the contract
13-
* call (address / function / arguments). Project-specific values (contract
14-
* addresses, amounts) are left as placeholders for the author to complete.
13+
* call (address / function / arguments). The catalog intentionally stays
14+
* small and follows the concrete registry and public-goods proposals used by
15+
* Tansu.
1516
*/
1617

1718
export type OutcomeType = "approved" | "rejected" | "cancelled";
@@ -40,147 +41,56 @@ export interface OutcomeTemplate {
4041
export const OUTCOME_TEMPLATES: OutcomeTemplate[] = [
4142
// ---------------------------------------------------------------- Approved
4243
{
43-
id: "approve-code-implementation",
44-
name: "Execute Code Implementation",
44+
id: "interact-stellar-registry",
45+
name: "Interact with the Stellar Registry",
4546
description:
46-
"For approved code proposals: merge the commit and record it on-chain",
47+
"Publish an approved contract hash to the main/root Stellar Registry",
4748
outcomeType: "approved",
48-
content: `# Approved: Code Implementation
49+
content: `# Approved: Interact with the Stellar Registry
4950
50-
The proposal was approved by the community. The proposed code changes will be merged and the commit hash will be recorded on-chain as the new repository revision.
51+
The proposal was approved by the community. The approved WASM hash will be published to the main/root Stellar Registry.
5152
5253
## Actions to Execute
53-
- [ ] Merge the approved commit [commit hash]
54-
- [ ] Record the commit hash on-chain
55-
- [ ] Publish the updated revision to the project repository
54+
- [ ] Resolve the approved contract through the Stellar Registry
55+
- [ ] Call \`publish_hash\` with the contract name, admin account, WASM hash, and version
56+
- [ ] Verify the published contract metadata on the registry
5657
5758
## Technical Details
58-
[Describe the XDR transaction or contract call that executes this outcome.]`,
59+
- Contract name: \`registry-tansu-manager\`
60+
- Admin account: \`GAMPJROHOAW662FINQ4XQOY2ULX5IEGYXCI4SMZYE75EHQBR6PSTJG3M\`
61+
- WASM hash: \`f13e2e9d329a1b5e72eed4c3203f98c36d513e9915de2482c229fbe4367e6591\`
62+
- Version: \`0.1.0\``,
5963
contract: {
6064
address: "",
61-
execute_fn: "record_commit",
62-
args: [""],
65+
execute_fn: "publish_hash",
66+
args: [
67+
"registry-tansu-manager",
68+
"GAMPJROHOAW662FINQ4XQOY2ULX5IEGYXCI4SMZYE75EHQBR6PSTJG3M",
69+
"f13e2e9d329a1b5e72eed4c3203f98c36d513e9915de2482c229fbe4367e6591",
70+
"0.1.0",
71+
],
6372
},
6473
},
6574
{
66-
id: "approve-governance-update",
67-
name: "Apply Governance Update",
75+
id: "public-goods-award",
76+
name: "Award / Public Goods Grant",
6877
description:
69-
"For approved governance proposals: apply the new rule on-chain",
78+
"Fund a public-goods maintenance proposal and report the payment",
7079
outcomeType: "approved",
71-
content: `# Approved: Governance Update
80+
content: `# Approved: Public Goods Award
7281
73-
The proposal was approved. The governance rule change will now be applied on-chain and will take effect for future proposals and votes.
82+
The proposal was approved by the community. The requested budget of \`20,000 XLM\` will be awarded to the public-goods project for the approved maintenance work.
7483
75-
## Actions to Execute
76-
- [ ] Update the governance parameter(s) described in the proposal
77-
- [ ] Confirm the new rules in the project configuration
78-
- [ ] Notify the community of the updated voting parameters`,
79-
contract: {
80-
address: "",
81-
execute_fn: "update_config",
82-
args: [""],
83-
},
84-
},
85-
{
86-
id: "approve-award-disbursement",
87-
name: "Disburse Award",
88-
description:
89-
"For approved award/budget proposals: execute the transfer on-chain",
90-
outcomeType: "approved",
91-
content: `# Approved: Award Disbursement
92-
93-
The proposal was approved. The requested budget will be disbursed to the beneficiary and the transfer will be executed on-chain.
94-
95-
## Actions to Execute
96-
- [ ] Transfer [amount] XLM to [beneficiary address]
97-
- [ ] Record the disbursement transaction hash
98-
- [ ] Publish the transfer proof and a short spending report`,
99-
contract: {
100-
address: "",
101-
execute_fn: "transfer",
102-
args: ["", 0],
103-
},
104-
},
105-
{
106-
id: "approve-membership-update",
107-
name: "Apply Membership Update",
108-
description:
109-
"For approved membership proposals: apply the maintainer/role change",
110-
outcomeType: "approved",
111-
content: `# Approved: Membership Update
112-
113-
The proposal was approved. The membership change will be applied to the project's maintainer list and role configuration.
114-
115-
## Actions to Execute
116-
- [ ] Add/remove the maintainer at address [address]
117-
- [ ] Update role permissions and voting weight
118-
- [ ] Confirm the new membership configuration on-chain`,
119-
contract: {
120-
address: "",
121-
execute_fn: "add_maintainer",
122-
args: [""],
123-
},
124-
},
125-
126-
// ---------------------------------------------------------------- Rejected
127-
{
128-
id: "reject-no-action",
129-
name: "No Action Taken",
130-
description:
131-
"Generic template for rejected proposals that require no execution",
132-
outcomeType: "rejected",
133-
content: `# Rejected: No Action Taken
134-
135-
The proposal was rejected by the community. No code changes will be merged and no on-chain action will be executed as a result of this proposal.
136-
137-
## Consequences
138-
- The current repository revision remains unchanged
139-
- No payments, rewards, or membership changes will be applied`,
140-
},
141-
{
142-
id: "reject-code-change",
143-
name: "Reject Code Change",
144-
description:
145-
"For rejected code proposals: the commit is not merged or recorded",
146-
outcomeType: "rejected",
147-
content: `# Rejected: Code Change Not Merged
148-
149-
The proposal was rejected. The proposed code changes will NOT be merged and the commit [commit hash] will not be recorded on-chain.
150-
151-
## Consequences
152-
- The repository stays on revision [current revision]
153-
- The author may revise the change and submit a new proposal`,
154-
},
155-
156-
// --------------------------------------------------------------- Cancelled
157-
{
158-
id: "cancelled-no-action",
159-
name: "No Action Taken",
160-
description:
161-
"Generic template for cancelled proposals that require no execution",
162-
outcomeType: "cancelled",
163-
content: `# Cancelled: No Action Taken
164-
165-
The proposal was cancelled before the voting period concluded. No actions will be executed and no state changes will be applied.
166-
167-
## Consequences
168-
- The current repository revision remains unchanged
169-
- No payments, rewards, or membership changes were applied`,
170-
},
171-
{
172-
id: "cancelled-resubmission",
173-
name: "Ready for Resubmission",
174-
description:
175-
"For cancelled proposals that can be revised and resubmitted later",
176-
outcomeType: "cancelled",
177-
content: `# Cancelled: Ready for Resubmission
178-
179-
The proposal was cancelled by the proposer or the maintainers. The subject may be revised and resubmitted as a new proposal in the future.
84+
## Award Details
85+
- Amount: \`20,000 XLM\`
86+
- Beneficiary: [beneficiary address or project treasury]
87+
- Payment: [describe the payment schedule or transaction]
18088
181-
## Notes
182-
- No on-chain action was executed
183-
- A new proposal should reference this cancellation for context`,
89+
## Payment and Reporting
90+
- [ ] Confirm the beneficiary and payment destination
91+
- [ ] Execute the approved payment
92+
- [ ] Record and publish the payment transaction hash
93+
- [ ] Publish progress and completion reports for the funded deliverables`,
18494
},
18595
];
18696

dapp/tests/unit/constants/outcomeTemplates.test.ts

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import {
1010
const OUTCOME_TYPES: OutcomeType[] = ["approved", "rejected", "cancelled"];
1111

1212
describe("OUTCOME_TEMPLATES", () => {
13-
it("covers all 3 outcome types", () => {
14-
for (const outcomeType of OUTCOME_TYPES) {
15-
const templates = OUTCOME_TEMPLATES.filter(
16-
(template) => template.outcomeType === outcomeType,
17-
);
18-
expect(templates.length).toBeGreaterThan(0);
19-
}
13+
it("contains only the curated registry and public-goods templates", () => {
14+
expect(OUTCOME_TEMPLATES).toHaveLength(2);
15+
expect(OUTCOME_TEMPLATES.map((template) => template.id)).toEqual([
16+
"interact-stellar-registry",
17+
"public-goods-award",
18+
]);
19+
expect(
20+
OUTCOME_TEMPLATES.every(
21+
(template) => template.outcomeType === "approved",
22+
),
23+
).toBe(true);
2024
});
2125

2226
it("has unique ids", () => {
@@ -43,31 +47,28 @@ describe("OUTCOME_TEMPLATES", () => {
4347
}
4448
});
4549

46-
it("pre-fills contract calls only on templates that execute an action", () => {
47-
// Approved templates execute real actions, so they should come with a
48-
// contract-call pre-fill (function + args) while the no-action rejected /
49-
// cancelled templates stay description-only.
50-
const approved = getOutcomeTemplatesByType("approved");
51-
for (const template of approved) {
52-
expect(template.contract).toBeDefined();
53-
expect(template.contract!.execute_fn.trim().length).toBeGreaterThan(0);
54-
expect(template.contract!.address).toBe(""); // filled by the author
55-
}
56-
57-
for (const type of ["rejected", "cancelled"] as const) {
58-
for (const template of getOutcomeTemplatesByType(type)) {
59-
expect(template.contract).toBeUndefined();
60-
expect(template.xdr).toBeUndefined();
61-
}
62-
}
50+
it("pre-fills the registry contract call and leaves its address for resolution", () => {
51+
const registry = getOutcomeTemplateById("interact-stellar-registry")!;
52+
expect(registry.contract).toEqual({
53+
address: "",
54+
execute_fn: "publish_hash",
55+
args: [
56+
"registry-tansu-manager",
57+
"GAMPJROHOAW662FINQ4XQOY2ULX5IEGYXCI4SMZYE75EHQBR6PSTJG3M",
58+
"f13e2e9d329a1b5e72eed4c3203f98c36d513e9915de2482c229fbe4367e6591",
59+
"0.1.0",
60+
],
61+
});
62+
expect(
63+
getOutcomeTemplateById("public-goods-award")?.contract,
64+
).toBeUndefined();
6365
});
6466
});
6567

6668
describe("getOutcomeTemplatesByType", () => {
6769
it("returns only templates matching the requested outcome type", () => {
6870
for (const outcomeType of OUTCOME_TYPES) {
6971
const templates = getOutcomeTemplatesByType(outcomeType);
70-
expect(templates.length).toBeGreaterThan(0);
7172
for (const template of templates) {
7273
expect(template.outcomeType).toBe(outcomeType);
7374
}
@@ -102,14 +103,16 @@ describe("getOutcomeTemplateFills", () => {
102103
});
103104

104105
it("reports contract-call pre-fill when the template has one", () => {
105-
const approved = getOutcomeTemplatesByType("approved");
106-
for (const template of approved) {
107-
expect(getOutcomeTemplateFills(template)).toContain("contract call");
108-
}
106+
expect(
107+
getOutcomeTemplateFills(
108+
getOutcomeTemplateById("interact-stellar-registry")!,
109+
),
110+
).toContain("contract call");
109111
});
110112

111113
it("reports only the description for no-action templates", () => {
112-
const rejected = getOutcomeTemplatesByType("rejected");
113-
expect(getOutcomeTemplateFills(rejected[0]!)).toEqual(["description"]);
114+
expect(
115+
getOutcomeTemplateFills(getOutcomeTemplateById("public-goods-award")!),
116+
).toEqual(["description"]);
114117
});
115118
});

0 commit comments

Comments
 (0)