@ixo/editor 5.31.1 → 5.32.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.
@@ -98,6 +98,9 @@ var CAN_TO_TYPE = {
98
98
  "matrix/dm": "qi/matrix.dm",
99
99
  "proposal/create": "qi/proposal.create",
100
100
  "proposal/vote": "qi/proposal.vote",
101
+ "governance/member-proposal": "qi/governance.member-proposal",
102
+ "governance/settings-proposal": "qi/governance.settings-proposal",
103
+ "governance.transaction/send-funds": "qi/governance.transaction.send-funds",
101
104
  "domain/card-preview": "qi/domain.card-preview",
102
105
  "domain/sign": "qi/domain.sign",
103
106
  "credential/store": "qi/credential.store",
@@ -698,6 +701,234 @@ registerAction({
698
701
  }
699
702
  });
700
703
 
704
+ // src/core/lib/actionRegistry/actions/governance/memberProposal.ts
705
+ var VALID_OPERATIONS = ["add", "remove", "update-weight"];
706
+ function defaultTitle(operation, count) {
707
+ const noun = count === 1 ? "member" : "members";
708
+ switch (operation) {
709
+ case "add":
710
+ return `Add ${count} ${noun}`;
711
+ case "remove":
712
+ return `Remove ${count} ${noun}`;
713
+ case "update-weight":
714
+ return `Update voting power for ${count} ${noun}`;
715
+ }
716
+ }
717
+ registerAction({
718
+ type: "qi/governance.member-proposal",
719
+ can: "governance/member-proposal",
720
+ sideEffect: true,
721
+ defaultRequiresConfirmation: true,
722
+ requiredCapability: "flow/block/execute",
723
+ outputSchema: [
724
+ { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
725
+ { path: "status", displayName: "Proposal Status", type: "string", description: "Current proposal status (open, passed, rejected, executed, etc.)" },
726
+ { path: "proposalContractAddress", displayName: "Proposal Contract Address", type: "string", description: "The proposal module contract address" },
727
+ { path: "coreAddress", displayName: "Core Address", type: "string", description: "The DAO core contract address" },
728
+ { path: "operation", displayName: "Operation", type: "string", description: "add | remove | update-weight" },
729
+ { path: "createdAt", displayName: "Created At", type: "string", description: "ISO timestamp of proposal creation" }
730
+ ],
731
+ run: async (inputs, ctx) => {
732
+ const handlers = ctx.handlers;
733
+ if (!handlers) {
734
+ throw new Error("Handlers not available");
735
+ }
736
+ const coreAddress = String(inputs.coreAddress || "").trim();
737
+ if (!coreAddress) throw new Error("coreAddress is required");
738
+ const operation = String(inputs.operation || "").trim();
739
+ if (!VALID_OPERATIONS.includes(operation)) {
740
+ throw new Error(`operation must be one of: ${VALID_OPERATIONS.join(", ")}`);
741
+ }
742
+ const rawMembers = Array.isArray(inputs.members) ? inputs.members : [];
743
+ if (rawMembers.length === 0) throw new Error("At least one member is required");
744
+ const normalized = rawMembers.map((m) => ({
745
+ addr: String(m.address || m.addr || "").trim(),
746
+ weight: Number(m.weight ?? 0)
747
+ }));
748
+ if (normalized.some((m) => !m.addr)) throw new Error("Every member needs an address");
749
+ const addrs = normalized.map((m) => m.addr);
750
+ if (new Set(addrs).size !== addrs.length) throw new Error("Duplicate member addresses are not allowed");
751
+ let add = [];
752
+ let remove = [];
753
+ if (operation === "remove") {
754
+ remove = normalized.map((m) => ({ addr: m.addr }));
755
+ } else {
756
+ if (normalized.some((m) => !Number.isFinite(m.weight) || m.weight <= 0)) {
757
+ throw new Error("Voting power must be a positive number");
758
+ }
759
+ add = normalized.map((m) => ({ addr: m.addr, weight: Math.trunc(m.weight) }));
760
+ }
761
+ const title = String(inputs.title || "").trim() || defaultTitle(operation, normalized.length);
762
+ const description = String(inputs.description || "").trim() || title;
763
+ const updateMembersAction = { type: "UpdateMembers", data: { add, remove } };
764
+ const { preProposalContractAddress } = await handlers.getPreProposalContractAddress({ coreAddress });
765
+ const { groupContractAddress } = await handlers.getGroupContractAddress({ coreAddress });
766
+ const { proposalContractAddress } = await handlers.getProposalContractAddress({ coreAddress });
767
+ const proposalId = await handlers.createProposal({
768
+ preProposalContractAddress,
769
+ title,
770
+ description,
771
+ actions: [updateMembersAction],
772
+ coreAddress,
773
+ groupContractAddress
774
+ });
775
+ if (proposalId === void 0 || proposalId === null || String(proposalId).trim() === "") {
776
+ throw new Error("Proposal creation returned no proposal id. Check the handler logs.");
777
+ }
778
+ return {
779
+ output: {
780
+ proposalId: String(proposalId),
781
+ status: "open",
782
+ proposalContractAddress: proposalContractAddress || "",
783
+ coreAddress,
784
+ operation,
785
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
786
+ }
787
+ };
788
+ }
789
+ });
790
+
791
+ // src/core/lib/actionRegistry/actions/governance/settingsProposal.ts
792
+ function clampPercent(value, label, { allowZero = false } = {}) {
793
+ const n = Number(value);
794
+ if (!Number.isFinite(n)) throw new Error(`${label} must be a number`);
795
+ if (n < 0 || n > 100) throw new Error(`${label} must be between 0 and 100`);
796
+ if (!allowZero && n <= 0) throw new Error(`${label} must be greater than 0`);
797
+ return n;
798
+ }
799
+ registerAction({
800
+ type: "qi/governance.settings-proposal",
801
+ can: "governance/settings-proposal",
802
+ sideEffect: true,
803
+ defaultRequiresConfirmation: true,
804
+ requiredCapability: "flow/block/execute",
805
+ outputSchema: [
806
+ { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
807
+ { path: "status", displayName: "Proposal Status", type: "string", description: "Current proposal status (open, passed, rejected, executed, etc.)" },
808
+ { path: "proposalContractAddress", displayName: "Proposal Contract Address", type: "string", description: "The proposal module contract address" },
809
+ { path: "coreAddress", displayName: "Core Address", type: "string", description: "The DAO core contract address" },
810
+ { path: "createdAt", displayName: "Created At", type: "string", description: "ISO timestamp of proposal creation" }
811
+ ],
812
+ run: async (inputs, ctx) => {
813
+ const handlers = ctx.handlers;
814
+ if (!handlers) {
815
+ throw new Error("Handlers not available");
816
+ }
817
+ const coreAddress = String(inputs.coreAddress || "").trim();
818
+ if (!coreAddress) throw new Error("coreAddress is required");
819
+ const votingPeriodHours = Number(inputs.votingPeriodHours);
820
+ if (!Number.isFinite(votingPeriodHours) || votingPeriodHours <= 0) {
821
+ throw new Error("votingPeriodHours must be a positive number");
822
+ }
823
+ const quorumPercent = clampPercent(inputs.quorumPercent, "quorumPercent", { allowZero: true });
824
+ const thresholdPercent = clampPercent(inputs.thresholdPercent, "thresholdPercent");
825
+ const vetoThresholdPercent = clampPercent(inputs.vetoThresholdPercent, "vetoThresholdPercent", { allowZero: true });
826
+ if (thresholdPercent + vetoThresholdPercent > 100) {
827
+ throw new Error("thresholdPercent + vetoThresholdPercent cannot exceed 100");
828
+ }
829
+ const allowRevoting = Boolean(inputs.allowRevoting);
830
+ const data = {
831
+ onlyMembersExecute: false,
832
+ thresholdType: "%",
833
+ thresholdPercentage: thresholdPercent,
834
+ quorumEnabled: quorumPercent > 0,
835
+ quorumType: "%",
836
+ quorumPercentage: quorumPercent,
837
+ proposalDuration: Math.trunc(votingPeriodHours),
838
+ proposalDurationUnits: "hours",
839
+ allowRevoting,
840
+ // TODO(governance): the editor's UpdateProposalConfigData / the consumer's
841
+ // message builder don't yet carry a veto threshold for single-choice
842
+ // voting. Passed through so the consumer can apply it once supported.
843
+ vetoThresholdPercentage: vetoThresholdPercent
844
+ };
845
+ const updateVotingConfigAction = { type: "UpdateVotingConfig", data };
846
+ const title = String(inputs.title || "").trim() || "Update governance settings";
847
+ const description = String(inputs.description || "").trim() || "Updates the group's voting rules once the proposal passes.";
848
+ const { preProposalContractAddress } = await handlers.getPreProposalContractAddress({ coreAddress });
849
+ const { groupContractAddress } = await handlers.getGroupContractAddress({ coreAddress });
850
+ const { proposalContractAddress } = await handlers.getProposalContractAddress({ coreAddress });
851
+ const proposalId = await handlers.createProposal({
852
+ preProposalContractAddress,
853
+ title,
854
+ description,
855
+ actions: [updateVotingConfigAction],
856
+ coreAddress,
857
+ groupContractAddress
858
+ });
859
+ if (proposalId === void 0 || proposalId === null || String(proposalId).trim() === "") {
860
+ throw new Error("Proposal creation returned no proposal id. Check the handler logs.");
861
+ }
862
+ return {
863
+ output: {
864
+ proposalId: String(proposalId),
865
+ status: "open",
866
+ proposalContractAddress: proposalContractAddress || "",
867
+ coreAddress,
868
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
869
+ }
870
+ };
871
+ }
872
+ });
873
+
874
+ // src/core/lib/actionRegistry/actions/governance/transactionSendFunds.ts
875
+ registerAction({
876
+ type: "qi/governance.transaction.send-funds",
877
+ can: "governance.transaction/send-funds",
878
+ sideEffect: true,
879
+ defaultRequiresConfirmation: true,
880
+ requiredCapability: "flow/block/execute",
881
+ outputSchema: [
882
+ { path: "proposalId", displayName: "Proposal ID", type: "string", description: "The on-chain proposal identifier" },
883
+ { path: "status", displayName: "Proposal Status", type: "string", description: "Current proposal status (open, passed, rejected, executed, etc.)" },
884
+ { path: "proposalContractAddress", displayName: "Proposal Contract Address", type: "string", description: "The proposal module contract address" },
885
+ { path: "coreAddress", displayName: "Core Address", type: "string", description: "The DAO core contract address" },
886
+ { path: "recipient", displayName: "Recipient", type: "string", description: "Destination address of the spend" },
887
+ { path: "createdAt", displayName: "Created At", type: "string", description: "ISO timestamp of proposal creation" }
888
+ ],
889
+ run: async (inputs, ctx) => {
890
+ const handlers = ctx.handlers;
891
+ if (!handlers) {
892
+ throw new Error("Handlers not available");
893
+ }
894
+ const coreAddress = String(inputs.coreAddress || "").trim();
895
+ if (!coreAddress) throw new Error("coreAddress is required");
896
+ const recipient = String(inputs.recipient || "").trim();
897
+ if (!recipient) throw new Error("recipient is required");
898
+ const denom = String(inputs.denom || "").trim();
899
+ if (!denom) throw new Error("denom is required");
900
+ const amount = Number(inputs.amount);
901
+ if (!Number.isFinite(amount) || amount <= 0) throw new Error("amount must be greater than 0");
902
+ const spendAction = { type: "Spend", data: { to: recipient, denom, amount: String(amount) } };
903
+ const title = String(inputs.title || "").trim() || `Send ${amount} ${denom} to ${recipient}`;
904
+ const description = String(inputs.description || "").trim() || "Sends funds from the group treasury once the proposal passes.";
905
+ const { preProposalContractAddress } = await handlers.getPreProposalContractAddress({ coreAddress });
906
+ const { groupContractAddress } = await handlers.getGroupContractAddress({ coreAddress });
907
+ const { proposalContractAddress } = await handlers.getProposalContractAddress({ coreAddress });
908
+ const proposalId = await handlers.createProposal({
909
+ preProposalContractAddress,
910
+ title,
911
+ description,
912
+ actions: [spendAction],
913
+ coreAddress,
914
+ groupContractAddress
915
+ });
916
+ if (proposalId === void 0 || proposalId === null || String(proposalId).trim() === "") {
917
+ throw new Error("Proposal creation returned no proposal id. Check the handler logs.");
918
+ }
919
+ return {
920
+ output: {
921
+ proposalId: String(proposalId),
922
+ status: "open",
923
+ proposalContractAddress: proposalContractAddress || "",
924
+ coreAddress,
925
+ recipient,
926
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
927
+ }
928
+ };
929
+ }
930
+ });
931
+
701
932
  // src/core/lib/actionRegistry/actions/httpRequest.ts
702
933
  registerAction({
703
934
  type: "qi/http.request",
@@ -11832,4 +12063,4 @@ export {
11832
12063
  executeQueuedFlowAgentCoreCommands,
11833
12064
  FlowAgentService
11834
12065
  };
11835
- //# sourceMappingURL=chunk-OP7EY4CE.mjs.map
12066
+ //# sourceMappingURL=chunk-BCQTKXDM.mjs.map