@continuumdao/ctm-mpc-defi 0.2.51 → 0.2.53

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.
@@ -64,7 +64,7 @@ When a proposal has a `forumKey`, fetch that thread (`ctm_continuum_dao_forum_re
64
64
 
65
65
  **A proposal MUST have a Governance forum thread.** Never call `build_propose_*` or `register_proposal` with the homepage, an empty `forumKey`, or an Ideas thread. Required agent order:
66
66
 
67
- 1. `forum_sign_in_eligible` then `build_forum_sign_in_multisign` (if no ticket).
67
+ 1. `get_forum_session` then `forum_me`. If `loggedIn`, reuse the ticket. Only if there is no session or `loggedIn` is false: `forum_sign_in_eligible` then `build_forum_sign_in_multisign`. Pass `address` on forum writes so the tool loads that node session before posting.
68
68
  2. If it is only an idea: `forum_create_idea`. Stop (no propose).
69
69
  3. If it is a proposal: `forum_create_topic` with the matching Governance `section` (proposal-threshold veCTM). Keep the returned `url`.
70
70
  4. Compose actions with `ctm_continuum_dao_compose_proposal_action` (prefer ABI from `etherscan__get_contract_abi` with chainid 59144/59141, type-check values, preview table). Then offer `ctm_continuum_dao_simulate_proposal` — pass `account` for another EOA, or `keyGenId` / omit `account` for the preferred KeyGen. Write `foundry.files` and run `foundry__forge_script` on a Linea fork with no `--broadcast`. Do **not** treat isolated `eth_call` as success. Do **not** submit if `ok` is false or a forge script reverts.
@@ -73,12 +73,12 @@ When a proposal has a `forumKey`, fetch that thread (`ctm_continuum_dao_forum_re
73
73
 
74
74
  `forumKey` must match `/topic/:tid` or `/t/:tid` on forum.continuumdao.org. Builders and `register_proposal` throw otherwise.
75
75
 
76
- Forum KeyGen sign-in is EIP-712 multi-sign (no EVM tx). Always check eligibility first:
76
+ Forum KeyGen sign-in is EIP-712 multi-sign (no EVM tx). The ticket is stored on the node. Always check that session first:
77
77
 
78
- 1. `ctm_continuum_dao_forum_sign_in_eligible({ address })` — holder or attach-key veCTM vs `veCtmThresholdPower`. If `eligible` is false, stop. Do **not** compose a multi-sign request.
79
- 2. `ctm_continuum_dao_build_forum_sign_in_multisign` also refuses ineligible addresses (same gate) so other nodes never enter the sign loop. Pass `nodeKey` (username = first 16 chars) or a `username` on first login.
80
- 3. After the multi-sign request is signed, exchange the signature for a ticket (`/api/continuum/eip712/ticket`). Then `forum_reply` / `forum_react` / `forum_create_topic` / `forum_create_idea`.
81
- 4. `ctm_continuum_dao_forum_sign_out({ ticket })` revokes the ticket and forum sessions. No multi-sign.
78
+ 1. `get_forum_session({ address })`. If `found` is true, `ctm_continuum_dao_forum_me({ ticket })`. If `loggedIn` is true, reuse that ticket. Do **not** compose a sign-in request.
79
+ 2. If there is no session or `loggedIn` is false: `ctm_continuum_dao_forum_sign_in_eligible({ address })` — current `balanceOfNFT` on an owned or attached veCTM vs `veCtmThresholdPower` (200 veCTM). Governor `getVotes` and month-start `balanceOfNFTAt` do not count. Read `votingPower`, `threshold`, and `tokenId` from the result. If `eligible` is false, stop. Do **not** compose a multi-sign request.
80
+ 3. `ctm_continuum_dao_build_forum_sign_in_multisign` uses the same gate and refuses ineligible addresses so other nodes never enter the sign loop. Always pass `nodeKey` from this node. A custom username does not omit it; `useNodeKeyUsername` only chooses the display name. Ask the operator for a custom username (2–16 letters, numbers, `.` `_` `-`) and pass `username` together with `nodeKey`. If they decline, call again with `useNodeKeyUsername: true` and the same `nodeKey`; the forum assigns the first 16 characters of the node key as the username. Do not invent a username and do not skip the question. After Execute, the node stores the ticket and the forum stores the verified node key on that user (new or existing), which is required for MPA Wallet Chat. Call `get_forum_session` again, then `forum_reply` / `forum_react` / `forum_create_topic` / `forum_create_idea`.
81
+ 4. `ctm_continuum_dao_forum_sign_out({ address, ticket })` deletes the node ticket (web chat and Telegram webhook turns share that row). `clear_forum_session({ address })` is the same delete. No multi-sign.
82
82
 
83
83
  Forum sign-in and sign-out are **agent/MCP only**. There is no Sign in / Sign out control in the node-app Compose header.
84
84
 
@@ -119,6 +119,7 @@ Propose builders **hard-fail** if `getVotes(KeyGen) <` live `proposalThreshold()
119
119
  | Fetch on-chain state | `ctm_continuum_dao_fetch_proposal_state` |
120
120
  | Who voted and how | `ctm_continuum_dao_fetch_proposal_votes` |
121
121
  | Fetch voting power | `ctm_continuum_dao_fetch_voting_power` |
122
+ | Forum session on this node | `get_forum_session` / `clear_forum_session` |
122
123
  | Forum sign-in eligible? | `ctm_continuum_dao_forum_sign_in_eligible` |
123
124
  | Forum KeyGen sign-in | `ctm_continuum_dao_build_forum_sign_in_multisign` |
124
125
  | Forum sign-out | `ctm_continuum_dao_forum_sign_out` |
@@ -187,7 +187,8 @@ var init_constants = __esm({
187
187
  [exports.CONTINUUM_DAO_LINEA_CHAIN_ID]: {
188
188
  ...UNSET,
189
189
  ctm: exports.CTM_TOKEN_ADDRESS,
190
- votingEscrow: exports.VOTING_ESCROW_LINEA_ADDRESS
190
+ votingEscrow: exports.VOTING_ESCROW_LINEA_ADDRESS,
191
+ governor: exports.CONTINUUM_DAO_LINEA_GOVERNOR_ADDRESS
191
192
  },
192
193
  [exports.CONTINUUM_DAO_LINEA_SEPOLIA_CHAIN_ID]: { ...UNSET },
193
194
  [exports.CONTINUUM_DAO_ETHEREUM_CHAIN_ID]: {
@@ -930,13 +931,38 @@ async function continuumDaoForumSignInEligible(args) {
930
931
  const st = body.status && typeof body.status === "object" ? body.status : null;
931
932
  const reason = typeof body.reason === "string" && body.reason || st && typeof st.message === "string" && st.message || void 0;
932
933
  const ok = res.ok && Boolean(body.ok ?? body.eligible);
934
+ const votingPower = typeof body.votingPower === "string" ? body.votingPower : void 0;
935
+ const threshold = typeof body.threshold === "string" ? body.threshold : void 0;
936
+ const tokenId = typeof body.tokenId === "string" ? body.tokenId : void 0;
933
937
  return {
934
938
  ok,
935
939
  eligible: ok,
936
940
  address: typeof body.address === "string" ? body.address : address,
937
- reason: ok ? void 0 : reason || "Address does not meet the veCTM threshold"
941
+ reason: ok ? void 0 : reason || "Address does not meet the veCTM threshold",
942
+ votingPower,
943
+ threshold,
944
+ tokenId
938
945
  };
939
946
  }
947
+ function assertForumSignInUsernameChoice(args) {
948
+ const nodeKey = args.nodeKey?.trim() || "";
949
+ const username = args.username?.trim() || "";
950
+ if (!nodeKey) {
951
+ throw new Error(
952
+ "KeyGen forum sign-in requires nodeKey from this node, including when the operator chose a custom username. useNodeKeyUsername only selects the username."
953
+ );
954
+ }
955
+ if (username && !FORUM_USERNAME_RE.test(username)) {
956
+ throw new Error(
957
+ "Ask the operator for a forum username of 2\u201316 letters, numbers, periods, underscores, or hyphens. If they want the node-key name, call again with useNodeKeyUsername true and the same nodeKey."
958
+ );
959
+ }
960
+ if (!username && !args.useNodeKeyUsername) {
961
+ throw new Error(
962
+ "Ask the operator for a custom forum username (2\u201316 letters, numbers, . _ -). If they decline, call again with useNodeKeyUsername true and the same nodeKey. The username will be the first 16 characters of the node key. Always pass nodeKey."
963
+ );
964
+ }
965
+ }
940
966
  async function continuumDaoForumSignInNonce(args) {
941
967
  const qs2 = new URLSearchParams({
942
968
  address: args.address,
@@ -945,6 +971,18 @@ async function continuumDaoForumSignInNonce(args) {
945
971
  });
946
972
  return forumFetch(`/api/continuum/eip712/nonce?${qs2}`, { forumUrl: args.forumUrl });
947
973
  }
974
+ function forumSignInTypedExpiryUnix(message) {
975
+ const raw = message.expiresAt;
976
+ const n = typeof raw === "number" ? raw : typeof raw === "string" && raw.trim() !== "" ? Number(raw) : NaN;
977
+ if (!Number.isFinite(n) || n <= 0) return void 0;
978
+ return Math.floor(n > 1e12 ? n / 1e3 : n);
979
+ }
980
+ function forumSignInRequestExpiryUnix(message, explicit) {
981
+ const typed = forumSignInTypedExpiryUnix(message);
982
+ const caller = explicit != null && explicit > 0 ? Math.floor(explicit) : void 0;
983
+ if (typed != null && caller != null) return Math.min(typed, caller);
984
+ return typed ?? caller;
985
+ }
948
986
  async function buildEvmMultisignBodyContinuumDaoForumSignIn(args) {
949
987
  const owner = viem.getAddress(args.executorAddress);
950
988
  const gate = await continuumDaoForumSignInEligible({ address: owner, forumUrl: args.forumUrl });
@@ -953,6 +991,7 @@ async function buildEvmMultisignBodyContinuumDaoForumSignIn(args) {
953
991
  `${gate.reason || "Address does not meet the veCTM threshold"}. Do not start a multi-sign request.`
954
992
  );
955
993
  }
994
+ assertForumSignInUsernameChoice(args);
956
995
  const typed = await continuumDaoForumSignInNonce({
957
996
  address: owner,
958
997
  nodeKey: args.nodeKey,
@@ -981,10 +1020,10 @@ async function buildEvmMultisignBodyContinuumDaoForumSignIn(args) {
981
1020
  }
982
1021
  }
983
1022
  ],
984
- expiryDate: args.expiryDate
1023
+ expiryDate: forumSignInRequestExpiryUnix(typed.message, args.expiryDate)
985
1024
  });
986
1025
  }
987
- var DEFAULT_FORUM_URL; exports.FORUM_GOVERNANCE_SECTIONS = void 0; exports.FORUM_IDEA_SECTION = void 0; exports.PROPOSAL_TYPE_TO_FORUM_SECTION = void 0; var IDEA_SECTION_ALIASES; exports.FORUM_LOGIN_DELIVERY_KIND = void 0;
1026
+ var DEFAULT_FORUM_URL; exports.FORUM_GOVERNANCE_SECTIONS = void 0; exports.FORUM_IDEA_SECTION = void 0; exports.PROPOSAL_TYPE_TO_FORUM_SECTION = void 0; var IDEA_SECTION_ALIASES; exports.FORUM_LOGIN_DELIVERY_KIND = void 0; var FORUM_USERNAME_RE;
988
1027
  var init_forum = __esm({
989
1028
  "src/protocols/evm/continuum-dao/forum.ts"() {
990
1029
  init_eip712Multisign();
@@ -1006,6 +1045,7 @@ var init_forum = __esm({
1006
1045
  };
1007
1046
  IDEA_SECTION_ALIASES = /* @__PURE__ */ new Set(["ideas", "ideas-suggestions", "suggestions"]);
1008
1047
  exports.FORUM_LOGIN_DELIVERY_KIND = "forumLogin";
1048
+ FORUM_USERNAME_RE = /^[A-Za-z0-9._-]{2,16}$/;
1009
1049
  }
1010
1050
  });
1011
1051