@continuumdao/ctm-mpc-defi 0.2.37 → 0.2.38

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.
Files changed (53) hide show
  1. package/dist/agent/catalog.cjs +49 -38
  2. package/dist/agent/catalog.cjs.map +1 -1
  3. package/dist/agent/catalog.d.ts +722 -2
  4. package/dist/agent/catalog.js +49 -38
  5. package/dist/agent/catalog.js.map +1 -1
  6. package/dist/agent/skills/continuum-dao/SKILL.md +18 -2
  7. package/dist/{buildBatch-CngvebiD.d.ts → buildBatch-tsEUVn_a.d.ts} +3 -3
  8. package/dist/chains/evm/index.cjs +33 -22
  9. package/dist/chains/evm/index.cjs.map +1 -1
  10. package/dist/chains/evm/index.d.ts +10 -6
  11. package/dist/chains/evm/index.js +33 -22
  12. package/dist/chains/evm/index.js.map +1 -1
  13. package/dist/chains/near/index.d.ts +1 -1
  14. package/dist/chains/solana/index.d.ts +1 -1
  15. package/dist/core/index.d.ts +5 -5
  16. package/dist/{eip712Multisign-DCW7heqX.d.ts → eip712Multisign-6oNIlObj.d.ts} +1 -1
  17. package/dist/{envelope-C_bfuKab.d.ts → envelope-Cm-llShH.d.ts} +1 -1
  18. package/dist/index.cjs +33 -22
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +6 -6
  21. package/dist/index.js +33 -22
  22. package/dist/index.js.map +1 -1
  23. package/dist/protocols/evm/aave-v4/index.d.ts +1 -1
  24. package/dist/protocols/evm/aerodrome/index.d.ts +4 -4
  25. package/dist/protocols/evm/arcus/index.d.ts +2 -2
  26. package/dist/protocols/evm/circle-cctp/index.d.ts +2 -2
  27. package/dist/protocols/evm/compound-v3/index.d.ts +1 -1
  28. package/dist/protocols/evm/continuum-dao/index.cjs +797 -46
  29. package/dist/protocols/evm/continuum-dao/index.cjs.map +1 -1
  30. package/dist/protocols/evm/continuum-dao/index.d.ts +189 -5
  31. package/dist/protocols/evm/continuum-dao/index.js +759 -48
  32. package/dist/protocols/evm/continuum-dao/index.js.map +1 -1
  33. package/dist/protocols/evm/curve-dao/index.d.ts +2 -2
  34. package/dist/protocols/evm/ethena/index.d.ts +1 -1
  35. package/dist/protocols/evm/euler-v2/index.cjs +33 -22
  36. package/dist/protocols/evm/euler-v2/index.cjs.map +1 -1
  37. package/dist/protocols/evm/euler-v2/index.d.ts +1 -1
  38. package/dist/protocols/evm/euler-v2/index.js +33 -22
  39. package/dist/protocols/evm/euler-v2/index.js.map +1 -1
  40. package/dist/protocols/evm/gmx/index.d.ts +2 -2
  41. package/dist/protocols/evm/hyperliquid/index.d.ts +2 -2
  42. package/dist/protocols/evm/lido/index.d.ts +1 -1
  43. package/dist/protocols/evm/maple/index.d.ts +1 -1
  44. package/dist/protocols/evm/morpho/index.d.ts +1 -1
  45. package/dist/protocols/evm/pendle/index.d.ts +4 -4
  46. package/dist/protocols/evm/permit2/index.d.ts +1 -1
  47. package/dist/protocols/evm/sky/index.d.ts +1 -1
  48. package/dist/protocols/evm/uniswap-v4/index.d.ts +3 -3
  49. package/dist/protocols/evm/venice/index.d.ts +1 -1
  50. package/dist/{registry-DxDSPQ-p.d.ts → registry-BvsXh0-r.d.ts} +1 -1
  51. package/dist/{types-RZWOTm8c.d.ts → types-RJpoU4Pc.d.ts} +1 -1
  52. package/dist/{types-PZrvtjv8.d.ts → types-lIfv59N1.d.ts} +2 -0
  53. package/package.json +1 -1
@@ -50,6 +50,11 @@ var VECTM_TOKEN_SYMBOL = "veCTM";
50
50
  var CTM_TOKEN_DECIMALS = 18;
51
51
  var WEEK_SECONDS = 7 * 86400;
52
52
  var MAXTIME_SECONDS = 4 * 365 * 86400;
53
+ var VECTM_MAX_LOCK_WEEKS = 208;
54
+ var VECTM_DEFAULT_MINIMUM_LOCK_WEI = 10n ** 18n;
55
+ var VECTM_LIQ_MIN_LOCKED_WEI = 100n;
56
+ var VECTM_LIQ_PENALTY_NUM = 50000n;
57
+ var VECTM_LIQ_PENALTY_DEN = 100000n;
53
58
  var UNSET = {
54
59
  ctm: viem.zeroAddress,
55
60
  votingEscrow: viem.zeroAddress,
@@ -57,15 +62,18 @@ var UNSET = {
57
62
  rewards: viem.zeroAddress,
58
63
  governor: viem.zeroAddress
59
64
  };
65
+ var CTM_TOKEN_ADDRESS = viem.getAddress("0x2026027054F5beBCEB650aBD62dC623e327658e7");
66
+ var VOTING_ESCROW_LINEA_ADDRESS = viem.getAddress("0x221EC90B3B083A8501A37bdeb7035CeaedF3C31f");
60
67
  var CONTINUUM_DAO_DEPLOYMENTS = {
61
- [CONTINUUM_DAO_LINEA_CHAIN_ID]: { ...UNSET },
68
+ [CONTINUUM_DAO_LINEA_CHAIN_ID]: {
69
+ ...UNSET,
70
+ ctm: CTM_TOKEN_ADDRESS,
71
+ votingEscrow: VOTING_ESCROW_LINEA_ADDRESS
72
+ },
62
73
  [CONTINUUM_DAO_LINEA_SEPOLIA_CHAIN_ID]: { ...UNSET },
63
74
  [CONTINUUM_DAO_ETHEREUM_CHAIN_ID]: {
64
- ctm: viem.zeroAddress,
65
- votingEscrow: viem.zeroAddress,
66
- nodeProperties: viem.zeroAddress,
67
- rewards: viem.zeroAddress,
68
- governor: viem.zeroAddress
75
+ ...UNSET,
76
+ ctm: CTM_TOKEN_ADDRESS
69
77
  }
70
78
  };
71
79
  var CONTINUUM_DAO_GOVERNANCE_API = "https://app-api.continuumdao.org";
@@ -156,6 +164,544 @@ function listContinuumDaoDefaultAssets() {
156
164
  }
157
165
  return out;
158
166
  }
167
+ function maxLockDurationSeconds() {
168
+ return Math.min(VECTM_MAX_LOCK_WEEKS * WEEK_SECONDS, MAXTIME_SECONDS);
169
+ }
170
+ function floorUnlockTimeSeconds(nowSec, durationSec) {
171
+ return (BigInt(nowSec) + durationSec) / BigInt(WEEK_SECONDS) * BigInt(WEEK_SECONDS);
172
+ }
173
+ function assertCtmAmountAtLeastMinimum(amountWei, minimumLockWei, label) {
174
+ const min = minimumLockWei > 0n ? minimumLockWei : VECTM_DEFAULT_MINIMUM_LOCK_WEI;
175
+ if (amountWei < min) {
176
+ throw new Error(`${label} must be at least ${formatCtmWei(min)} CTM (VotingEscrow.minimumLock).`);
177
+ }
178
+ }
179
+ function assertCreateLockAmount(amountWei, minimumLockWei, balanceWei) {
180
+ if (amountWei <= 0n) throw new Error("CTM amount must be greater than zero.");
181
+ assertCtmAmountAtLeastMinimum(amountWei, minimumLockWei, "Create lock");
182
+ if (balanceWei != null && amountWei > balanceWei) {
183
+ throw new Error(`Create lock ${formatCtmWei(amountWei)} CTM exceeds KeyGen balance ${formatCtmWei(balanceWei)} CTM.`);
184
+ }
185
+ }
186
+ function assertLockDurationSeconds(durationSec, nowSec, label) {
187
+ if (durationSec <= 0n) throw new Error(`${label} duration must be greater than zero.`);
188
+ if (durationSec < BigInt(WEEK_SECONDS)) {
189
+ throw new Error(`${label} duration must be at least 1 week (${WEEK_SECONDS} seconds).`);
190
+ }
191
+ if (durationSec > BigInt(maxLockDurationSeconds())) {
192
+ throw new Error(
193
+ `${label} duration cannot exceed ${VECTM_MAX_LOCK_WEEKS} weeks (VotingEscrow MAXTIME, week-aligned).`
194
+ );
195
+ }
196
+ const unlock = floorUnlockTimeSeconds(nowSec, durationSec);
197
+ if (unlock <= BigInt(nowSec)) {
198
+ throw new Error(`${label} unlock time must be after the current week boundary.`);
199
+ }
200
+ if (unlock > BigInt(nowSec) + BigInt(MAXTIME_SECONDS)) {
201
+ throw new Error(`${label} unlock time exceeds VotingEscrow MAXTIME (4 years).`);
202
+ }
203
+ return unlock;
204
+ }
205
+ function assertIncreaseAmount(amountWei, lock, nowSec, balanceWei) {
206
+ if (amountWei <= 0n) throw new Error("CTM amount must be greater than zero.");
207
+ if (lock.amountWei === 0n) throw new Error(`veCTM #${lock.tokenId} has no existing lock.`);
208
+ if (lock.endSec <= BigInt(nowSec)) {
209
+ throw new Error(`Cannot increase amount on expired veCTM #${lock.tokenId}. Withdraw or create a new lock.`);
210
+ }
211
+ if (balanceWei != null && amountWei > balanceWei) {
212
+ throw new Error(
213
+ `Increase amount ${formatCtmWei(amountWei)} CTM exceeds KeyGen balance ${formatCtmWei(balanceWei)} CTM.`
214
+ );
215
+ }
216
+ }
217
+ function assertIncreaseUnlockTime(durationSec, lock, nowSec) {
218
+ if (lock.amountWei === 0n) throw new Error(`veCTM #${lock.tokenId} has no existing lock.`);
219
+ if (lock.endSec <= BigInt(nowSec)) {
220
+ throw new Error(`Cannot increase unlock time on expired veCTM #${lock.tokenId}.`);
221
+ }
222
+ const unlock = assertLockDurationSeconds(durationSec, nowSec, "Increase unlock time");
223
+ if (unlock <= lock.endSec) {
224
+ throw new Error(
225
+ `New unlock time must be after the current lock end (${lock.endSec.toString()}). Pick a later week-aligned date.`
226
+ );
227
+ }
228
+ return unlock;
229
+ }
230
+ function assertSplitExtracted(extractedWei, lock, minimumLockWei, nowSec) {
231
+ if (lock.attached) {
232
+ throw new Error(`Split is blocked while veCTM #${lock.tokenId} is attached to a node. Request detach first.`);
233
+ }
234
+ if (lock.amountWei === 0n) throw new Error(`veCTM #${lock.tokenId} has no existing lock.`);
235
+ if (lock.endSec <= BigInt(nowSec)) {
236
+ throw new Error(`Cannot split expired veCTM #${lock.tokenId}.`);
237
+ }
238
+ const min = minimumLockWei > 0n ? minimumLockWei : VECTM_DEFAULT_MINIMUM_LOCK_WEI;
239
+ if (lock.amountWei < min * 2n) {
240
+ throw new Error("Split requires at least 2 CTM locked (1 CTM minimum per resulting lock).");
241
+ }
242
+ if (extractedWei < min) {
243
+ throw new Error(`Extract at least ${formatCtmWei(min)} CTM into the new lock.`);
244
+ }
245
+ if (extractedWei > lock.amountWei - min) {
246
+ throw new Error(`The original lock must keep at least ${formatCtmWei(min)} CTM after the split.`);
247
+ }
248
+ }
249
+ function assertMerge(fromLock, toLock) {
250
+ if (fromLock.tokenId === toLock.tokenId) throw new Error("Merge requires two different token ids.");
251
+ if (fromLock.attached) {
252
+ throw new Error(`Merge is blocked while veCTM #${fromLock.tokenId} is attached to a node.`);
253
+ }
254
+ if (toLock.attached) {
255
+ throw new Error(`Merge is blocked while veCTM #${toLock.tokenId} is attached to a node.`);
256
+ }
257
+ if (fromLock.owner.toLowerCase() !== toLock.owner.toLowerCase()) {
258
+ throw new Error("Merge requires both veCTM locks to have the same owner.");
259
+ }
260
+ }
261
+ function assertWithdraw(lock, nowSec) {
262
+ if (lock.attached) {
263
+ throw new Error(`Withdraw is blocked while veCTM #${lock.tokenId} is attached to a node. Request detach first.`);
264
+ }
265
+ if (lock.endSec <= 0n) throw new Error(`Unlock time unavailable for veCTM #${lock.tokenId}.`);
266
+ if (lock.endSec > BigInt(nowSec)) {
267
+ throw new Error(`Withdraw is available after the lock expires (unlock ${lock.endSec.toString()}).`);
268
+ }
269
+ }
270
+ function computeLiquidationAmounts(args) {
271
+ if (args.lockEndSec > 0n && BigInt(args.nowSec) >= args.lockEndSec) {
272
+ return {
273
+ userWei: args.lockedAmountWei,
274
+ treasuryWei: 0n,
275
+ lockExpired: true,
276
+ tooSmall: false,
277
+ zeroPenalty: false
278
+ };
279
+ }
280
+ if (args.lockedAmountWei <= VECTM_LIQ_MIN_LOCKED_WEI) {
281
+ return {
282
+ userWei: 0n,
283
+ treasuryWei: 0n,
284
+ lockExpired: false,
285
+ tooSmall: true,
286
+ zeroPenalty: false
287
+ };
288
+ }
289
+ const penalty = args.vePowerWei * VECTM_LIQ_PENALTY_NUM / VECTM_LIQ_PENALTY_DEN;
290
+ if (penalty === 0n) {
291
+ return {
292
+ userWei: 0n,
293
+ treasuryWei: 0n,
294
+ lockExpired: false,
295
+ tooSmall: false,
296
+ zeroPenalty: true
297
+ };
298
+ }
299
+ return {
300
+ userWei: args.lockedAmountWei - penalty,
301
+ treasuryWei: penalty,
302
+ lockExpired: false,
303
+ tooSmall: false,
304
+ zeroPenalty: false
305
+ };
306
+ }
307
+ function assertLiquidate(lock, liquidationsEnabled, nowSec) {
308
+ if (lock.attached) {
309
+ throw new Error(`Liquidate is blocked while veCTM #${lock.tokenId} is attached to a node. Request detach first.`);
310
+ }
311
+ if (liquidationsEnabled === false) {
312
+ throw new Error("Liquidations are currently disabled on-chain.");
313
+ }
314
+ const preview = computeLiquidationAmounts({
315
+ lockedAmountWei: lock.amountWei,
316
+ vePowerWei: lock.vePowerWei,
317
+ lockEndSec: lock.endSec,
318
+ nowSec
319
+ });
320
+ if (preview.tooSmall) throw new Error("Locked amount is too small to liquidate.");
321
+ if (preview.zeroPenalty) throw new Error("Voting power is too low to compute a liquidation penalty.");
322
+ }
323
+ function assertDelegatee(delegateeRaw, executor) {
324
+ const delegatee = viem.getAddress(delegateeRaw);
325
+ if (delegatee === viem.zeroAddress) {
326
+ throw new Error("Zero address is not allowed. Use undelegate to return power to this KeyGen.");
327
+ }
328
+ const self = viem.getAddress(executor);
329
+ if (delegatee === self) {
330
+ throw new Error("This is this KeyGen\u2019s address \u2014 use undelegate instead.");
331
+ }
332
+ return delegatee;
333
+ }
334
+ function assertUndelegateNotAlreadySelf(alreadySelf, account) {
335
+ if (alreadySelf) {
336
+ throw new Error(`Voting power is already at this KeyGen (${viem.getAddress(account)}).`);
337
+ }
338
+ }
339
+ function assertTransfer(lock, toRaw, fromRaw) {
340
+ if (lock.attached) {
341
+ throw new Error(`Transfer is blocked while veCTM #${lock.tokenId} is attached to a node.`);
342
+ }
343
+ const to = viem.getAddress(toRaw);
344
+ const from = viem.getAddress(fromRaw);
345
+ if (to === viem.zeroAddress) throw new Error("Cannot transfer veCTM to the zero address.");
346
+ if (to === from) throw new Error("Transfer recipient must be different from this KeyGen.");
347
+ return to;
348
+ }
349
+ function assertExecutorOwnsLock(lock, executor) {
350
+ if (lock.owner.toLowerCase() !== viem.getAddress(executor).toLowerCase()) {
351
+ throw new Error(`KeyGen ${viem.getAddress(executor)} does not own veCTM #${lock.tokenId} (owner ${lock.owner}).`);
352
+ }
353
+ }
354
+ function formatCtmWei(wei) {
355
+ const whole = wei / 10n ** 18n;
356
+ const frac = wei % 10n ** 18n;
357
+ if (frac === 0n) return whole.toString();
358
+ const fracStr = frac.toString().padStart(18, "0").replace(/0+$/, "");
359
+ return `${whole.toString()}.${fracStr}`;
360
+ }
361
+ var veReadAbi = viem.parseAbi([
362
+ "function minimumLock() view returns (uint256)",
363
+ "function locked(uint256 tokenId) view returns (int128 amount, uint256 end)",
364
+ "function ownerOf(uint256 tokenId) view returns (address)",
365
+ "function balanceOfNFT(uint256 tokenId) view returns (uint256)",
366
+ "function liquidationsEnabled() view returns (bool)",
367
+ "function nodeProperties() view returns (address)",
368
+ "function rewards() view returns (address)",
369
+ "function delegates(address account) view returns (address)",
370
+ "function token() view returns (address)"
371
+ ]);
372
+ var erc20Abi = viem.parseAbi(["function balanceOf(address owner) view returns (uint256)"]);
373
+ var nodePropsAbi = viem.parseAbi([
374
+ "function attachedKeyGen(uint256 tokenId) view returns (address)",
375
+ "function attachedTokenId(address keyGen) view returns (uint256)",
376
+ "function nodeRequestingDetachment(uint256 tokenId) view returns (bool)"
377
+ ]);
378
+ var rewardsAbi = viem.parseAbi(["function unclaimedRewards(uint256 tokenId) view returns (uint256)"]);
379
+ var mpaReadAbi = viem.parseAbi([
380
+ "function getNodeWithdrawAuthority(string nodeKey) view returns (address)",
381
+ "function ve() view returns (address)",
382
+ "function veCtmThresholdPower() view returns (uint256)",
383
+ "function nodeProperties() view returns (address)"
384
+ ]);
385
+ function publicClient(rpcUrl, chainId) {
386
+ return viem.createPublicClient({
387
+ chain: viem.defineChain({
388
+ id: chainId,
389
+ name: "ContinuumDAO escrow",
390
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
391
+ rpcUrls: { default: { http: [rpcUrl.trim()] } }
392
+ }),
393
+ transport: viem.http(rpcUrl.trim())
394
+ });
395
+ }
396
+ async function readMinimumLockWei(args) {
397
+ const client = publicClient(args.rpcUrl, args.chainId);
398
+ try {
399
+ const min = await client.readContract({
400
+ address: args.votingEscrow,
401
+ abi: veReadAbi,
402
+ functionName: "minimumLock"
403
+ });
404
+ return min > 0n ? min : VECTM_DEFAULT_MINIMUM_LOCK_WEI;
405
+ } catch {
406
+ return VECTM_DEFAULT_MINIMUM_LOCK_WEI;
407
+ }
408
+ }
409
+ async function readCtmBalanceWei(args) {
410
+ const client = publicClient(args.rpcUrl, args.chainId);
411
+ try {
412
+ return await client.readContract({
413
+ address: args.ctm,
414
+ abi: erc20Abi,
415
+ functionName: "balanceOf",
416
+ args: [args.owner]
417
+ });
418
+ } catch {
419
+ return void 0;
420
+ }
421
+ }
422
+ async function readVeCtmLockState(args) {
423
+ const client = publicClient(args.rpcUrl, args.chainId);
424
+ const [lockedPair, owner, vePower, nodeProperties] = await Promise.all([
425
+ client.readContract({
426
+ address: args.votingEscrow,
427
+ abi: veReadAbi,
428
+ functionName: "locked",
429
+ args: [args.tokenId]
430
+ }),
431
+ client.readContract({
432
+ address: args.votingEscrow,
433
+ abi: veReadAbi,
434
+ functionName: "ownerOf",
435
+ args: [args.tokenId]
436
+ }),
437
+ client.readContract({
438
+ address: args.votingEscrow,
439
+ abi: veReadAbi,
440
+ functionName: "balanceOfNFT",
441
+ args: [args.tokenId]
442
+ }),
443
+ client.readContract({
444
+ address: args.votingEscrow,
445
+ abi: veReadAbi,
446
+ functionName: "nodeProperties"
447
+ })
448
+ ]);
449
+ const amountRaw = lockedPair[0];
450
+ const amountWei = amountRaw < 0n ? 0n : BigInt(amountRaw);
451
+ let attached = false;
452
+ if (isConfiguredAddress(nodeProperties)) {
453
+ try {
454
+ const attachedKey = await client.readContract({
455
+ address: viem.getAddress(nodeProperties),
456
+ abi: nodePropsAbi,
457
+ functionName: "attachedKeyGen",
458
+ args: [args.tokenId]
459
+ });
460
+ attached = attachedKey !== viem.zeroAddress;
461
+ } catch {
462
+ attached = false;
463
+ }
464
+ }
465
+ return {
466
+ tokenId: args.tokenId,
467
+ amountWei,
468
+ endSec: lockedPair[1],
469
+ owner: viem.getAddress(owner),
470
+ attached,
471
+ vePowerWei: vePower
472
+ };
473
+ }
474
+ async function assertNoUnclaimedRewards(args) {
475
+ const client = publicClient(args.rpcUrl, args.chainId);
476
+ let rewards;
477
+ try {
478
+ rewards = await client.readContract({
479
+ address: args.votingEscrow,
480
+ abi: veReadAbi,
481
+ functionName: "rewards"
482
+ });
483
+ } catch {
484
+ return;
485
+ }
486
+ if (!isConfiguredAddress(rewards)) return;
487
+ try {
488
+ const unclaimed = await client.readContract({
489
+ address: rewards,
490
+ abi: rewardsAbi,
491
+ functionName: "unclaimedRewards",
492
+ args: [args.tokenId]
493
+ });
494
+ if (unclaimed !== 0n) {
495
+ throw new Error(`veCTM #${args.tokenId} has unclaimed rewards. Claim them before this escrow action.`);
496
+ }
497
+ } catch (err) {
498
+ if (err instanceof Error && err.message.includes("unclaimed rewards")) throw err;
499
+ }
500
+ }
501
+ async function preflightCreateLock(args) {
502
+ const nowSec = Math.floor(Date.now() / 1e3);
503
+ const [minimumLockWei, balanceWei] = await Promise.all([
504
+ readMinimumLockWei(args),
505
+ readCtmBalanceWei(args)
506
+ ]);
507
+ assertCreateLockAmount(args.amountWei, minimumLockWei, balanceWei);
508
+ assertLockDurationSeconds(args.durationSec, nowSec, "Create lock");
509
+ }
510
+ async function preflightIncreaseAmount(args) {
511
+ const nowSec = Math.floor(Date.now() / 1e3);
512
+ const [lock, balanceWei] = await Promise.all([
513
+ readVeCtmLockState(args),
514
+ readCtmBalanceWei(args)
515
+ ]);
516
+ assertExecutorOwnsLock(lock, args.owner);
517
+ assertIncreaseAmount(args.amountWei, lock, nowSec, balanceWei);
518
+ await assertNoUnclaimedRewards(args);
519
+ }
520
+ async function preflightIncreaseUnlockTime(args) {
521
+ const nowSec = Math.floor(Date.now() / 1e3);
522
+ const lock = await readVeCtmLockState(args);
523
+ assertExecutorOwnsLock(lock, args.owner);
524
+ assertIncreaseUnlockTime(args.durationSec, lock, nowSec);
525
+ await assertNoUnclaimedRewards(args);
526
+ }
527
+ async function preflightSplit(args) {
528
+ const nowSec = Math.floor(Date.now() / 1e3);
529
+ const [lock, minimumLockWei] = await Promise.all([
530
+ readVeCtmLockState(args),
531
+ readMinimumLockWei(args)
532
+ ]);
533
+ assertExecutorOwnsLock(lock, args.owner);
534
+ assertSplitExtracted(args.extractedWei, lock, minimumLockWei, nowSec);
535
+ await assertNoUnclaimedRewards(args);
536
+ }
537
+ async function preflightMerge(args) {
538
+ const [fromLock, toLock] = await Promise.all([
539
+ readVeCtmLockState({ ...args, tokenId: args.fromTokenId }),
540
+ readVeCtmLockState({ ...args, tokenId: args.toTokenId })
541
+ ]);
542
+ assertExecutorOwnsLock(fromLock, args.owner);
543
+ assertExecutorOwnsLock(toLock, args.owner);
544
+ assertMerge(fromLock, toLock);
545
+ await Promise.all([
546
+ assertNoUnclaimedRewards({ ...args, tokenId: args.fromTokenId }),
547
+ assertNoUnclaimedRewards({ ...args, tokenId: args.toTokenId })
548
+ ]);
549
+ }
550
+ async function preflightWithdraw(args) {
551
+ const nowSec = Math.floor(Date.now() / 1e3);
552
+ const lock = await readVeCtmLockState(args);
553
+ assertExecutorOwnsLock(lock, args.owner);
554
+ assertWithdraw(lock, nowSec);
555
+ await assertNoUnclaimedRewards(args);
556
+ }
557
+ async function preflightLiquidate(args) {
558
+ const nowSec = Math.floor(Date.now() / 1e3);
559
+ const client = publicClient(args.rpcUrl, args.chainId);
560
+ const [lock, liquidationsEnabled] = await Promise.all([
561
+ readVeCtmLockState(args),
562
+ client.readContract({
563
+ address: args.votingEscrow,
564
+ abi: veReadAbi,
565
+ functionName: "liquidationsEnabled"
566
+ }).catch(() => null)
567
+ ]);
568
+ assertExecutorOwnsLock(lock, args.owner);
569
+ assertLiquidate(lock, liquidationsEnabled, nowSec);
570
+ await assertNoUnclaimedRewards(args);
571
+ }
572
+ async function preflightDelegate(args) {
573
+ return assertDelegatee(args.delegatee, args.owner);
574
+ }
575
+ async function preflightUndelegate(args) {
576
+ const client = publicClient(args.rpcUrl, args.chainId);
577
+ const delegatee = await client.readContract({
578
+ address: args.votingEscrow,
579
+ abi: veReadAbi,
580
+ functionName: "delegates",
581
+ args: [args.owner]
582
+ });
583
+ assertUndelegateNotAlreadySelf(viem.getAddress(delegatee) === viem.getAddress(args.owner), args.owner);
584
+ }
585
+ async function preflightTransfer(args) {
586
+ const lock = await readVeCtmLockState(args);
587
+ assertExecutorOwnsLock(lock, args.owner);
588
+ return assertTransfer(lock, args.to, args.owner);
589
+ }
590
+ async function preflightAttachVeCtm(args) {
591
+ const client = publicClient(args.rpcUrl, args.chainId);
592
+ const [authority, veFromWallet, threshold, nodeProperties] = await Promise.all([
593
+ client.readContract({
594
+ address: args.mpaWallet,
595
+ abi: mpaReadAbi,
596
+ functionName: "getNodeWithdrawAuthority",
597
+ args: [args.nodeKey]
598
+ }),
599
+ client.readContract({ address: args.mpaWallet, abi: mpaReadAbi, functionName: "ve" }),
600
+ client.readContract({
601
+ address: args.mpaWallet,
602
+ abi: mpaReadAbi,
603
+ functionName: "veCtmThresholdPower"
604
+ }),
605
+ client.readContract({
606
+ address: args.mpaWallet,
607
+ abi: mpaReadAbi,
608
+ functionName: "nodeProperties"
609
+ })
610
+ ]);
611
+ const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : viem.getAddress(veFromWallet);
612
+ if (!isConfiguredAddress(ve)) throw new Error("veCTM is not live on the fee contract yet.");
613
+ const lock = await readVeCtmLockState({
614
+ rpcUrl: args.rpcUrl,
615
+ chainId: args.chainId,
616
+ votingEscrow: ve,
617
+ tokenId: args.tokenId
618
+ });
619
+ assertExecutorOwnsLock(lock, args.owner);
620
+ const executor = viem.getAddress(args.owner);
621
+ if (authority === viem.zeroAddress || viem.getAddress(authority) !== executor) {
622
+ throw new Error(
623
+ `Compose attachVeCtm from the claimed authority KeyGen (${authority}), which must own the NFT.`
624
+ );
625
+ }
626
+ if (isConfiguredAddress(nodeProperties)) {
627
+ const np = viem.getAddress(nodeProperties);
628
+ const [attachedKey, attachedForKey] = await Promise.all([
629
+ client.readContract({
630
+ address: np,
631
+ abi: nodePropsAbi,
632
+ functionName: "attachedKeyGen",
633
+ args: [args.tokenId]
634
+ }),
635
+ client.readContract({
636
+ address: np,
637
+ abi: nodePropsAbi,
638
+ functionName: "attachedTokenId",
639
+ args: [executor]
640
+ })
641
+ ]);
642
+ if (attachedKey !== viem.zeroAddress) {
643
+ throw new Error(`veCTM #${args.tokenId} is already attached. You cannot replace an attached NFT.`);
644
+ }
645
+ if (attachedForKey !== 0n) {
646
+ throw new Error(
647
+ `This KeyGen already has veCTM #${attachedForKey} attached. Request detach and wait for governance. A second group on this node attaches after rotating nodeWithdrawAuthority to that group's secp256k1 KeyGen.`
648
+ );
649
+ }
650
+ }
651
+ if (threshold > 0n && lock.amountWei < threshold) {
652
+ throw new Error(
653
+ `Locked CTM on veCTM #${args.tokenId} is below MultiSignAgentWallet.veCtmThresholdPower (${threshold.toString()} wei).`
654
+ );
655
+ }
656
+ }
657
+ async function preflightRequestDetach(args) {
658
+ const client = publicClient(args.rpcUrl, args.chainId);
659
+ const [attachedKey, alreadyRequested] = await Promise.all([
660
+ client.readContract({
661
+ address: args.nodeProperties,
662
+ abi: nodePropsAbi,
663
+ functionName: "attachedKeyGen",
664
+ args: [args.tokenId]
665
+ }),
666
+ client.readContract({
667
+ address: args.nodeProperties,
668
+ abi: nodePropsAbi,
669
+ functionName: "nodeRequestingDetachment",
670
+ args: [args.tokenId]
671
+ })
672
+ ]);
673
+ if (attachedKey === viem.zeroAddress) {
674
+ throw new Error(`veCTM #${args.tokenId} is not attached to a node.`);
675
+ }
676
+ if (alreadyRequested) {
677
+ throw new Error(
678
+ `Detach already requested for veCTM #${args.tokenId}. The NFT stays attached until governance executes detach.`
679
+ );
680
+ }
681
+ if (viem.getAddress(attachedKey) !== viem.getAddress(args.owner)) {
682
+ throw new Error(
683
+ `Request detach from the attached KeyGen (${viem.getAddress(attachedKey)}).`
684
+ );
685
+ }
686
+ }
687
+
688
+ // src/protocols/evm/continuum-dao/attachGroup.ts
689
+ var VECTM_NODE_INFO_TUPLE = "(string,string,uint8[4],uint16[8],string,uint256,uint256,string,string,bytes)";
690
+ var ATTACH_VECTM_SIGNATURE = `attachVeCtm(string,uint256,${VECTM_NODE_INFO_TUPLE},string)`;
691
+ function resolveAttachGroupId(keyGen, explicit) {
692
+ const fromKg = (keyGen.groupId ?? keyGen.GroupId ?? "").trim();
693
+ const fromArg = (explicit ?? "").trim();
694
+ if (fromKg && fromArg && fromKg !== fromArg) {
695
+ throw new Error(
696
+ `groupId must be the attaching KeyGen's mpc-auth GroupId (${fromKg}), not a different group.`
697
+ );
698
+ }
699
+ if (fromKg) return fromKg;
700
+ if (fromArg) return fromArg;
701
+ throw new Error(
702
+ "groupId is required to attach veCTM (use the attaching KeyGen's mpc-auth GroupId)."
703
+ );
704
+ }
159
705
 
160
706
  // src/core/purpose.ts
161
707
  function mergePurposeText(purposeText, purposeSuffix) {
@@ -247,25 +793,25 @@ async function buildEvmMultisignBatch(args) {
247
793
  nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
248
794
  rpcUrls: { default: { http: [rpcUrl] } }
249
795
  });
250
- const publicClient2 = viem.createPublicClient({ chain: ch, transport: viem.http(rpcUrl) });
796
+ const publicClient3 = viem.createPublicClient({ chain: ch, transport: viem.http(rpcUrl) });
251
797
  const feeParams = await continuumNodeSdk.fetchChainFeeParams(rpcUrl, chainId);
252
798
  const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
253
- const latestBaseFeeWei = !legacy ? (await publicClient2.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
799
+ const latestBaseFeeWei = !legacy ? (await publicClient3.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
254
800
  const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
255
801
  const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
256
802
  const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
257
803
  const executor = viem.getAddress(executorAddress);
258
- const baseNonce = await publicClient2.getTransactionCount({ address: executor, blockTag: "pending" });
804
+ const baseNonce = await publicClient3.getTransactionCount({ address: executor, blockTag: "pending" });
259
805
  const legs = [];
260
806
  for (let i = 0; i < steps.length; i++) {
261
807
  const step = steps[i];
262
808
  const currentNonce = baseNonce + i;
263
809
  let estimatedGas;
264
810
  if (args.estimateGasForStep) {
265
- estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient: publicClient2, executor });
811
+ estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient: publicClient3, executor });
266
812
  } else {
267
813
  try {
268
- estimatedGas = await publicClient2.estimateGas({
814
+ estimatedGas = await publicClient3.estimateGas({
269
815
  to: step.to,
270
816
  data: step.data,
271
817
  value: step.value,
@@ -277,7 +823,7 @@ async function buildEvmMultisignBatch(args) {
277
823
  }
278
824
  let gasLimitI;
279
825
  if (args.resolveGasLimit) {
280
- gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient: publicClient2 });
826
+ gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient: publicClient3 });
281
827
  } else if (step.routerSwap) {
282
828
  gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
283
829
  } else {
@@ -287,7 +833,7 @@ async function buildEvmMultisignBatch(args) {
287
833
  let feeSnapshot;
288
834
  let serialized;
289
835
  if (legacy) {
290
- let gasPriceWei = await publicClient2.getGasPrice();
836
+ let gasPriceWei = await publicClient3.getGasPrice();
291
837
  if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
292
838
  gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
293
839
  }
@@ -404,9 +950,7 @@ var CONTINUUM_DAO_DELEGATE_FALLBACK = 160000n;
404
950
  var CONTINUUM_DAO_TRANSFER_FALLBACK = 160000n;
405
951
  var CONTINUUM_DAO_ATTACH_FALLBACK = 450000n;
406
952
  var CONTINUUM_DAO_DETACH_FALLBACK = 220000n;
407
- var VECTM_NODE_INFO_TUPLE = "(string,string,uint8[4],uint16[8],string,uint256,uint256,string,string,bytes)";
408
- var ATTACH_VECTM_SIGNATURE = `attachVeCtm(string,uint256,${VECTM_NODE_INFO_TUPLE})`;
409
- var erc20Abi = viem.parseAbi([
953
+ var erc20Abi2 = viem.parseAbi([
410
954
  "function allowance(address owner, address spender) view returns (uint256)",
411
955
  "function approve(address spender, uint256 amount) returns (bool)"
412
956
  ]);
@@ -422,7 +966,7 @@ var veWriteAbi = viem.parseAbi([
422
966
  "function transferFrom(address _from, address _to, uint256 _tokenId)"
423
967
  ]);
424
968
  var attachAbi = viem.parseAbi([
425
- `function attachVeCtm(string nodeKey, uint256 tokenId, ${VECTM_NODE_INFO_TUPLE} nodeInfo)`
969
+ `function attachVeCtm(string nodeKey, uint256 tokenId, ${VECTM_NODE_INFO_TUPLE} nodeInfo, string groupId)`
426
970
  ]);
427
971
  var detachAbi = viem.parseAbi(["function setNodeRemovalStatus(uint256 tokenId, bool status)"]);
428
972
  var CONTINUUM_DAO_EVM_TYPES = /* @__PURE__ */ new Set([
@@ -513,7 +1057,7 @@ async function maybeApproveCtmStep(args) {
513
1057
  try {
514
1058
  allowance = await client.readContract({
515
1059
  address: args.ctm,
516
- abi: erc20Abi,
1060
+ abi: erc20Abi2,
517
1061
  functionName: "allowance",
518
1062
  args: [args.owner, args.spender]
519
1063
  });
@@ -522,7 +1066,7 @@ async function maybeApproveCtmStep(args) {
522
1066
  }
523
1067
  if (allowance >= args.amountWei) return null;
524
1068
  const data = viem.encodeFunctionData({
525
- abi: erc20Abi,
1069
+ abi: erc20Abi2,
526
1070
  functionName: "approve",
527
1071
  args: [args.spender, args.amountWei]
528
1072
  });
@@ -542,10 +1086,17 @@ async function buildEvmMultisignBodyContinuumDaoCreateLock(args) {
542
1086
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
543
1087
  const ctm = args.ctm && isConfiguredAddress(args.ctm) ? viem.getAddress(args.ctm) : requireCtm(args.chainId);
544
1088
  const amountWei = viem.parseUnits(String(args.amountHuman).trim().replace(/,/g, ""), CTM_TOKEN_DECIMALS);
545
- if (amountWei <= 0n) throw new Error("CTM amount must be greater than zero.");
546
1089
  const duration = BigInt(String(args.lockDurationSeconds).trim());
547
- if (duration <= 0n) throw new Error("Lock duration must be greater than zero.");
548
1090
  const owner = viem.getAddress(args.executorAddress);
1091
+ await preflightCreateLock({
1092
+ rpcUrl: args.rpcUrl,
1093
+ chainId: args.chainId,
1094
+ votingEscrow: ve,
1095
+ ctm,
1096
+ owner,
1097
+ amountWei,
1098
+ durationSec: duration
1099
+ });
549
1100
  const approve = await maybeApproveCtmStep({
550
1101
  rpcUrl: args.rpcUrl,
551
1102
  chainId: args.chainId,
@@ -578,8 +1129,16 @@ async function buildEvmMultisignBodyContinuumDaoIncreaseAmount(args) {
578
1129
  const ctm = args.ctm && isConfiguredAddress(args.ctm) ? viem.getAddress(args.ctm) : requireCtm(args.chainId);
579
1130
  const tokenId = parseTokenId(args.tokenId);
580
1131
  const amountWei = viem.parseUnits(String(args.amountHuman).trim().replace(/,/g, ""), CTM_TOKEN_DECIMALS);
581
- if (amountWei <= 0n) throw new Error("CTM amount must be greater than zero.");
582
1132
  const owner = viem.getAddress(args.executorAddress);
1133
+ await preflightIncreaseAmount({
1134
+ rpcUrl: args.rpcUrl,
1135
+ chainId: args.chainId,
1136
+ votingEscrow: ve,
1137
+ ctm,
1138
+ owner,
1139
+ tokenId,
1140
+ amountWei
1141
+ });
583
1142
  const approve = await maybeApproveCtmStep({
584
1143
  rpcUrl: args.rpcUrl,
585
1144
  chainId: args.chainId,
@@ -611,7 +1170,15 @@ async function buildEvmMultisignBodyContinuumDaoIncreaseUnlockTime(args) {
611
1170
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
612
1171
  const tokenId = parseTokenId(args.tokenId);
613
1172
  const duration = BigInt(String(args.lockDurationSeconds).trim());
614
- if (duration <= 0n) throw new Error("Unlock duration must be greater than zero.");
1173
+ const owner = viem.getAddress(args.executorAddress);
1174
+ await preflightIncreaseUnlockTime({
1175
+ rpcUrl: args.rpcUrl,
1176
+ chainId: args.chainId,
1177
+ votingEscrow: ve,
1178
+ owner,
1179
+ tokenId,
1180
+ durationSec: duration
1181
+ });
615
1182
  const data = viem.encodeFunctionData({
616
1183
  abi: veWriteAbi,
617
1184
  functionName: "increase_unlock_time",
@@ -634,7 +1201,14 @@ async function buildEvmMultisignBodyContinuumDaoSplit(args) {
634
1201
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
635
1202
  const tokenId = parseTokenId(args.tokenId);
636
1203
  const extracted = viem.parseUnits(String(args.extractedHuman).trim().replace(/,/g, ""), CTM_TOKEN_DECIMALS);
637
- if (extracted <= 0n) throw new Error("Split amount must be greater than zero.");
1204
+ await preflightSplit({
1205
+ rpcUrl: args.rpcUrl,
1206
+ chainId: args.chainId,
1207
+ votingEscrow: ve,
1208
+ owner: viem.getAddress(args.executorAddress),
1209
+ tokenId,
1210
+ extractedWei: extracted
1211
+ });
638
1212
  const data = viem.encodeFunctionData({
639
1213
  abi: veWriteAbi,
640
1214
  functionName: "split",
@@ -657,7 +1231,14 @@ async function buildEvmMultisignBodyContinuumDaoMerge(args) {
657
1231
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
658
1232
  const fromId = parseTokenId(args.fromTokenId);
659
1233
  const toId = parseTokenId(args.toTokenId);
660
- if (fromId === toId) throw new Error("Merge requires two different token ids.");
1234
+ await preflightMerge({
1235
+ rpcUrl: args.rpcUrl,
1236
+ chainId: args.chainId,
1237
+ votingEscrow: ve,
1238
+ owner: viem.getAddress(args.executorAddress),
1239
+ fromTokenId: fromId,
1240
+ toTokenId: toId
1241
+ });
661
1242
  const data = viem.encodeFunctionData({
662
1243
  abi: veWriteAbi,
663
1244
  functionName: "merge",
@@ -679,6 +1260,13 @@ async function buildEvmMultisignBodyContinuumDaoMerge(args) {
679
1260
  async function buildEvmMultisignBodyContinuumDaoWithdraw(args) {
680
1261
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
681
1262
  const tokenId = parseTokenId(args.tokenId);
1263
+ await preflightWithdraw({
1264
+ rpcUrl: args.rpcUrl,
1265
+ chainId: args.chainId,
1266
+ votingEscrow: ve,
1267
+ owner: viem.getAddress(args.executorAddress),
1268
+ tokenId
1269
+ });
682
1270
  const data = viem.encodeFunctionData({ abi: veWriteAbi, functionName: "withdraw", args: [tokenId] });
683
1271
  const step = {
684
1272
  to: ve,
@@ -695,6 +1283,13 @@ async function buildEvmMultisignBodyContinuumDaoWithdraw(args) {
695
1283
  async function buildEvmMultisignBodyContinuumDaoLiquidate(args) {
696
1284
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
697
1285
  const tokenId = parseTokenId(args.tokenId);
1286
+ await preflightLiquidate({
1287
+ rpcUrl: args.rpcUrl,
1288
+ chainId: args.chainId,
1289
+ votingEscrow: ve,
1290
+ owner: viem.getAddress(args.executorAddress),
1291
+ tokenId
1292
+ });
698
1293
  const data = viem.encodeFunctionData({ abi: veWriteAbi, functionName: "liquidate", args: [tokenId] });
699
1294
  const step = {
700
1295
  to: ve,
@@ -710,7 +1305,12 @@ async function buildEvmMultisignBodyContinuumDaoLiquidate(args) {
710
1305
  }
711
1306
  async function buildEvmMultisignBodyContinuumDaoDelegate(args) {
712
1307
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
713
- const delegatee = viem.getAddress(args.delegatee);
1308
+ const delegatee = await preflightDelegate({
1309
+ rpcUrl: args.rpcUrl,
1310
+ chainId: args.chainId,
1311
+ owner: viem.getAddress(args.executorAddress),
1312
+ delegatee: args.delegatee
1313
+ });
714
1314
  const data = viem.encodeFunctionData({ abi: veWriteAbi, functionName: "delegate", args: [delegatee] });
715
1315
  const step = {
716
1316
  to: ve,
@@ -727,6 +1327,12 @@ async function buildEvmMultisignBodyContinuumDaoDelegate(args) {
727
1327
  async function buildEvmMultisignBodyContinuumDaoUndelegate(args) {
728
1328
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
729
1329
  const self = viem.getAddress(args.executorAddress);
1330
+ await preflightUndelegate({
1331
+ rpcUrl: args.rpcUrl,
1332
+ chainId: args.chainId,
1333
+ votingEscrow: ve,
1334
+ owner: self
1335
+ });
730
1336
  const data = viem.encodeFunctionData({ abi: veWriteAbi, functionName: "delegate", args: [self] });
731
1337
  const step = {
732
1338
  to: ve,
@@ -741,7 +1347,7 @@ async function buildEvmMultisignBodyContinuumDaoUndelegate(args) {
741
1347
  };
742
1348
  return finalize([step], args, `ContinuumDAO: undelegate voting power (delegate back to ${self}).`);
743
1349
  }
744
- var veReadAbi = viem.parseAbi(["function delegates(address account) view returns (address)"]);
1350
+ var veReadAbi2 = viem.parseAbi(["function delegates(address account) view returns (address)"]);
745
1351
  async function continuumDaoFetchDelegates(args) {
746
1352
  const chainId = Number(args.chainId);
747
1353
  requireLockChain(chainId);
@@ -757,15 +1363,22 @@ async function continuumDaoFetchDelegates(args) {
757
1363
  transport: viem.http(args.rpcUrl.trim())
758
1364
  });
759
1365
  const delegatee = viem.getAddress(
760
- await client.readContract({ address: ve, abi: veReadAbi, functionName: "delegates", args: [account] })
1366
+ await client.readContract({ address: ve, abi: veReadAbi2, functionName: "delegates", args: [account] })
761
1367
  );
762
1368
  return { account, delegatee, self: delegatee === account };
763
1369
  }
764
1370
  async function buildEvmMultisignBodyContinuumDaoTransfer(args) {
765
1371
  const ve = args.votingEscrow && isConfiguredAddress(args.votingEscrow) ? viem.getAddress(args.votingEscrow) : requireVe(args.chainId);
766
1372
  const tokenId = parseTokenId(args.tokenId);
767
- const to = viem.getAddress(args.to);
768
1373
  const from = viem.getAddress(args.executorAddress);
1374
+ const to = await preflightTransfer({
1375
+ rpcUrl: args.rpcUrl,
1376
+ chainId: args.chainId,
1377
+ votingEscrow: ve,
1378
+ owner: from,
1379
+ tokenId,
1380
+ to: args.to
1381
+ });
769
1382
  const data = viem.encodeFunctionData({
770
1383
  abi: veWriteAbi,
771
1384
  functionName: "transferFrom",
@@ -826,11 +1439,20 @@ async function buildEvmMultisignBodyContinuumDaoAttachVeCtm(args) {
826
1439
  const tokenId = parseTokenId(args.tokenId);
827
1440
  const nodeKey = args.nodeKey.trim();
828
1441
  if (!nodeKey) throw new Error("nodeKey is required to attach veCTM.");
1442
+ const groupId = resolveAttachGroupId(args.keyGen, args.groupId);
1443
+ await preflightAttachVeCtm({
1444
+ rpcUrl: args.rpcUrl,
1445
+ chainId: args.chainId,
1446
+ mpaWallet: wallet,
1447
+ nodeKey,
1448
+ tokenId,
1449
+ owner: viem.getAddress(args.executorAddress)
1450
+ });
829
1451
  const tuple = args.nodeInfoTuple ?? encodeContinuumDaoNodeInfo(args.nodeInfo);
830
1452
  const data = viem.encodeFunctionData({
831
1453
  abi: attachAbi,
832
1454
  functionName: "attachVeCtm",
833
- args: [nodeKey, tokenId, tuple]
1455
+ args: [nodeKey, tokenId, tuple, groupId]
834
1456
  });
835
1457
  const step = {
836
1458
  to: wallet,
@@ -840,16 +1462,24 @@ async function buildEvmMultisignBodyContinuumDaoAttachVeCtm(args) {
840
1462
  buildBatchMeta: ({ gasLimit }) => meta("continuum_dao_attach", args.chainId, wallet, {
841
1463
  name: "MultiSignAgentWallet.attachVeCtm",
842
1464
  nodeKey,
843
- tokenId: tokenId.toString()
1465
+ tokenId: tokenId.toString(),
1466
+ groupId
844
1467
  }, gasLimit)
845
1468
  };
846
- return finalize([step], args, `ContinuumDAO: attach veCTM #${tokenId}.`);
1469
+ return finalize([step], args, `ContinuumDAO: attach veCTM #${tokenId} (group ${groupId}).`);
847
1470
  }
848
1471
  async function buildEvmMultisignBodyContinuumDaoRequestDetach(args) {
849
1472
  requireLockChain(args.chainId);
850
1473
  const np = viem.getAddress(args.nodeProperties);
851
1474
  if (np === viem.zeroAddress) throw new Error("NodeProperties address is required to request detach.");
852
1475
  const tokenId = parseTokenId(args.tokenId);
1476
+ await preflightRequestDetach({
1477
+ rpcUrl: args.rpcUrl,
1478
+ chainId: args.chainId,
1479
+ nodeProperties: np,
1480
+ owner: viem.getAddress(args.executorAddress),
1481
+ tokenId
1482
+ });
853
1483
  const data = viem.encodeFunctionData({
854
1484
  abi: detachAbi,
855
1485
  functionName: "setNodeRemovalStatus",
@@ -1449,7 +2079,9 @@ var governorReadAbi = viem.parseAbi([
1449
2079
  "function proposalThreshold() view returns (uint256)",
1450
2080
  "function proposalCount() view returns (uint256)",
1451
2081
  "function clock() view returns (uint48)",
1452
- "function proposalVotes(uint256 proposalId) view returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes)"
2082
+ "function proposalVotes(uint256 proposalId) view returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes)",
2083
+ "function hasVoted(uint256 proposalId, address account) view returns (bool)",
2084
+ "function proposalSnapshot(uint256 proposalId) view returns (uint256)"
1453
2085
  ]);
1454
2086
  function splitAbiParamList(inner) {
1455
2087
  const out = [];
@@ -1701,9 +2333,32 @@ async function buildEvmMultisignBodyContinuumDaoProposeDelta(args) {
1701
2333
  );
1702
2334
  return { ...built, proposalArgs };
1703
2335
  }
2336
+ async function requireVoteEligible(args) {
2337
+ const st = await continuumDaoFetchProposalState({
2338
+ chainId: args.chainId,
2339
+ rpcUrl: args.rpcUrl,
2340
+ proposalId: args.proposalId,
2341
+ governor: args.governor
2342
+ });
2343
+ if (st.state !== 1) {
2344
+ throw new Error(`Proposal is not Active (on-chain state ${st.state}).`);
2345
+ }
2346
+ const elig = await continuumDaoFetchProposalVoteEligibility({
2347
+ chainId: args.chainId,
2348
+ rpcUrl: args.rpcUrl,
2349
+ proposalId: args.proposalId,
2350
+ account: args.executorAddress,
2351
+ governor: args.governor
2352
+ });
2353
+ if (elig.hasVoted) throw new Error("This KeyGen has already voted on this proposal.");
2354
+ if (BigInt(elig.snapshotVotes) === 0n) {
2355
+ throw new Error("No veCTM voting power at snapshot for this KeyGen.");
2356
+ }
2357
+ }
1704
2358
  async function buildEvmMultisignBodyContinuumDaoCastVoteBravo(args) {
1705
2359
  const gov = requireGovernor(args.chainId, args.governor);
1706
2360
  const proposalId = BigInt(String(args.proposalId).trim());
2361
+ await requireVoteEligible(args);
1707
2362
  const data = viem.encodeFunctionData({
1708
2363
  abi: governorWriteAbi,
1709
2364
  functionName: "castVote",
@@ -1722,6 +2377,10 @@ async function buildEvmMultisignBodyContinuumDaoCastVoteBravo(args) {
1722
2377
  async function buildEvmMultisignBodyContinuumDaoCastVoteDelta(args) {
1723
2378
  const gov = requireGovernor(args.chainId, args.governor);
1724
2379
  const proposalId = BigInt(String(args.proposalId).trim());
2380
+ if (args.weights.length < 3) {
2381
+ throw new Error("Delta vote must include one weight per option plus a NOTA slot.");
2382
+ }
2383
+ await requireVoteEligible(args);
1725
2384
  const params = encodeDeltaVoteParams(args.weights);
1726
2385
  const data = viem.encodeFunctionData({
1727
2386
  abi: governorWriteAbi,
@@ -1774,7 +2433,7 @@ async function buildEvmMultisignBodyContinuumDaoCancel(args) {
1774
2433
  "continuum-dao cancel"
1775
2434
  );
1776
2435
  }
1777
- function publicClient(rpcUrl, chainId) {
2436
+ function publicClient2(rpcUrl, chainId) {
1778
2437
  return viem.createPublicClient({
1779
2438
  chain: viem.defineChain({
1780
2439
  id: chainId,
@@ -1788,7 +2447,7 @@ function publicClient(rpcUrl, chainId) {
1788
2447
  async function continuumDaoHashProposal(args) {
1789
2448
  const chainId = Number(args.chainId);
1790
2449
  const gov = requireGovernor(chainId, args.governor);
1791
- const client = publicClient(args.rpcUrl, chainId);
2450
+ const client = publicClient2(args.rpcUrl, chainId);
1792
2451
  const id = await client.readContract({
1793
2452
  address: gov,
1794
2453
  abi: governorReadAbi,
@@ -1800,7 +2459,7 @@ async function continuumDaoHashProposal(args) {
1800
2459
  async function continuumDaoFetchVotingPower(args) {
1801
2460
  const chainId = Number(args.chainId);
1802
2461
  const gov = requireGovernor(chainId, args.governor);
1803
- const client = publicClient(args.rpcUrl, chainId);
2462
+ const client = publicClient2(args.rpcUrl, chainId);
1804
2463
  const clock = await client.readContract({ address: gov, abi: governorReadAbi, functionName: "clock" });
1805
2464
  const timepoint = clock > 0 ? clock - 1 : 0;
1806
2465
  const [votes, threshold] = await Promise.all([
@@ -1817,22 +2476,74 @@ async function continuumDaoFetchVotingPower(args) {
1817
2476
  async function continuumDaoFetchProposalState(args) {
1818
2477
  const chainId = Number(args.chainId);
1819
2478
  const gov = requireGovernor(chainId, args.governor);
1820
- const client = publicClient(args.rpcUrl, chainId);
2479
+ const client = publicClient2(args.rpcUrl, chainId);
1821
2480
  const proposalId = BigInt(String(args.proposalId).trim());
1822
- const [state, votes] = await Promise.all([
1823
- client.readContract({ address: gov, abi: governorReadAbi, functionName: "state", args: [proposalId] }),
1824
- client.readContract({ address: gov, abi: governorReadAbi, functionName: "proposalVotes", args: [proposalId] })
1825
- ]);
2481
+ const state = await client.readContract({
2482
+ address: gov,
2483
+ abi: governorReadAbi,
2484
+ functionName: "state",
2485
+ args: [proposalId]
2486
+ });
2487
+ let againstVotes = "0";
2488
+ let forVotes = "0";
2489
+ let abstainVotes = "0";
2490
+ let votesPartial = false;
2491
+ try {
2492
+ const votes = await client.readContract({
2493
+ address: gov,
2494
+ abi: governorReadAbi,
2495
+ functionName: "proposalVotes",
2496
+ args: [proposalId]
2497
+ });
2498
+ againstVotes = votes[0].toString();
2499
+ forVotes = votes[1].toString();
2500
+ abstainVotes = votes[2].toString();
2501
+ } catch {
2502
+ votesPartial = true;
2503
+ }
1826
2504
  return {
1827
2505
  state: Number(state),
1828
- againstVotes: votes[0].toString(),
1829
- forVotes: votes[1].toString(),
1830
- abstainVotes: votes[2].toString()
2506
+ againstVotes,
2507
+ forVotes,
2508
+ abstainVotes,
2509
+ votesPartial
2510
+ };
2511
+ }
2512
+ async function continuumDaoFetchProposalVoteEligibility(args) {
2513
+ const chainId = Number(args.chainId);
2514
+ const gov = requireGovernor(chainId, args.governor);
2515
+ const client = publicClient2(args.rpcUrl, chainId);
2516
+ const proposalId = BigInt(String(args.proposalId).trim());
2517
+ const account = viem.getAddress(args.account);
2518
+ const [hasVoted, snapshotBlock] = await Promise.all([
2519
+ client.readContract({
2520
+ address: gov,
2521
+ abi: governorReadAbi,
2522
+ functionName: "hasVoted",
2523
+ args: [proposalId, account]
2524
+ }),
2525
+ client.readContract({
2526
+ address: gov,
2527
+ abi: governorReadAbi,
2528
+ functionName: "proposalSnapshot",
2529
+ args: [proposalId]
2530
+ })
2531
+ ]);
2532
+ const snapshotVotes = await client.readContract({
2533
+ address: gov,
2534
+ abi: governorReadAbi,
2535
+ functionName: "getVotes",
2536
+ args: [account, snapshotBlock]
2537
+ });
2538
+ return {
2539
+ hasVoted,
2540
+ snapshotVotes: snapshotVotes.toString(),
2541
+ snapshotBlock: snapshotBlock.toString()
1831
2542
  };
1832
2543
  }
1833
2544
  async function continuumDaoFetchProposalCount(args) {
1834
2545
  const gov = requireGovernor(args.chainId, args.governor);
1835
- const client = publicClient(args.rpcUrl, args.chainId);
2546
+ const client = publicClient2(args.rpcUrl, args.chainId);
1836
2547
  const count = await client.readContract({ address: gov, abi: governorReadAbi, functionName: "proposalCount" });
1837
2548
  return { count: count.toString() };
1838
2549
  }
@@ -2167,7 +2878,7 @@ var continuumDaoProtocolModule = {
2167
2878
  { id: "continuum-dao.undelegate", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Return voting power to the KeyGen (delegate to self)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: {} },
2168
2879
  { id: "continuum-dao.fetch-delegates", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Read VotingEscrow.delegates(account)", commonParams: [], params: { account: { type: "string", required: true, description: "KeyGen or wallet address" } } },
2169
2880
  { id: "continuum-dao.transfer", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Transfer a veCTM NFT", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { tokenId: { type: "string", required: true, description: "veCTM token id" }, to: { type: "string", required: true, description: "Recipient" } } },
2170
- { id: "continuum-dao.attach", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Attach veCTM to the node (authority KeyGen)", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { nodeKey: { type: "string", required: true, description: "Node key" }, tokenId: { type: "string", required: true, description: "veCTM token id" }, mpaWallet: { type: "string", required: true, description: "MultiSignAgentWallet address" } } },
2881
+ { id: "continuum-dao.attach", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Attach veCTM to the node (authority KeyGen). groupId is implicit from that KeyGen and binds the fee waiver. A second group attaches after rotating authority.", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { nodeKey: { type: "string", required: true, description: "Node key" }, tokenId: { type: "string", required: true, description: "veCTM token id" }, mpaWallet: { type: "string", required: true, description: "MultiSignAgentWallet address" } } },
2171
2882
  { id: "continuum-dao.request-detach", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Request governance detach", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { tokenId: { type: "string", required: true, description: "Attached token id" }, nodeProperties: { type: "string", required: true, description: "NodeProperties address" } } },
2172
2883
  { id: "continuum-dao.propose-bravo", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Propose a Bravo (For/Against/Abstain) proposal", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { title: { type: "string", required: true, description: "On-chain description (title)" }, forumKey: { type: "string", required: true, description: "Created forum thread URL from forum_create_topic" } } },
2173
2884
  { id: "continuum-dao.propose-delta", protocolId: CONTINUUM_DAO_PROTOCOL_ID, chainCategory: "evm", description: "Propose a Delta multi-option proposal", commonParams: ["keyGen", "purposeText", "useCustomGas"], params: { title: { type: "string", required: true, description: "On-chain description (title)" }, nWinners: { type: "number", required: true, description: "Winning options to execute" }, forumKey: { type: "string", required: true, description: "Created forum thread URL from forum_create_topic" } } },
@@ -2217,6 +2928,7 @@ exports.CONTINUUM_DAO_SPLIT_FALLBACK = CONTINUUM_DAO_SPLIT_FALLBACK;
2217
2928
  exports.CONTINUUM_DAO_TRANSFER_FALLBACK = CONTINUUM_DAO_TRANSFER_FALLBACK;
2218
2929
  exports.CONTINUUM_DAO_VOTE_FALLBACK = CONTINUUM_DAO_VOTE_FALLBACK;
2219
2930
  exports.CONTINUUM_DAO_WITHDRAW_FALLBACK = CONTINUUM_DAO_WITHDRAW_FALLBACK;
2931
+ exports.CTM_TOKEN_ADDRESS = CTM_TOKEN_ADDRESS;
2220
2932
  exports.CTM_TOKEN_DECIMALS = CTM_TOKEN_DECIMALS;
2221
2933
  exports.CTM_TOKEN_NAME = CTM_TOKEN_NAME;
2222
2934
  exports.CTM_TOKEN_SYMBOL = CTM_TOKEN_SYMBOL;
@@ -2228,9 +2940,15 @@ exports.GOVERNOR_STATE_LABELS = GOVERNOR_STATE_LABELS;
2228
2940
  exports.MAXTIME_SECONDS = MAXTIME_SECONDS;
2229
2941
  exports.PROPOSAL_TYPE_LABELS = PROPOSAL_TYPE_LABELS;
2230
2942
  exports.PROPOSAL_TYPE_TO_FORUM_SECTION = PROPOSAL_TYPE_TO_FORUM_SECTION;
2943
+ exports.VECTM_DEFAULT_MINIMUM_LOCK_WEI = VECTM_DEFAULT_MINIMUM_LOCK_WEI;
2944
+ exports.VECTM_LIQ_MIN_LOCKED_WEI = VECTM_LIQ_MIN_LOCKED_WEI;
2945
+ exports.VECTM_LIQ_PENALTY_DEN = VECTM_LIQ_PENALTY_DEN;
2946
+ exports.VECTM_LIQ_PENALTY_NUM = VECTM_LIQ_PENALTY_NUM;
2947
+ exports.VECTM_MAX_LOCK_WEEKS = VECTM_MAX_LOCK_WEEKS;
2231
2948
  exports.VECTM_NODE_INFO_TUPLE = VECTM_NODE_INFO_TUPLE;
2232
2949
  exports.VECTM_TOKEN_NAME = VECTM_TOKEN_NAME;
2233
2950
  exports.VECTM_TOKEN_SYMBOL = VECTM_TOKEN_SYMBOL;
2951
+ exports.VOTING_ESCROW_LINEA_ADDRESS = VOTING_ESCROW_LINEA_ADDRESS;
2234
2952
  exports.WEEK_SECONDS = WEEK_SECONDS;
2235
2953
  exports.applyForumSectionCheck = applyForumSectionCheck;
2236
2954
  exports.assertContinuumDaoForumDeleteInput = assertContinuumDaoForumDeleteInput;
@@ -2238,6 +2956,19 @@ exports.assertContinuumDaoForumMarkReadInput = assertContinuumDaoForumMarkReadIn
2238
2956
  exports.assertContinuumDaoForumRecentInput = assertContinuumDaoForumRecentInput;
2239
2957
  exports.assertContinuumDaoForumSearchInput = assertContinuumDaoForumSearchInput;
2240
2958
  exports.assertContinuumDaoForumTopicKey = assertContinuumDaoForumTopicKey;
2959
+ exports.assertCreateLockAmount = assertCreateLockAmount;
2960
+ exports.assertCtmAmountAtLeastMinimum = assertCtmAmountAtLeastMinimum;
2961
+ exports.assertDelegatee = assertDelegatee;
2962
+ exports.assertExecutorOwnsLock = assertExecutorOwnsLock;
2963
+ exports.assertIncreaseAmount = assertIncreaseAmount;
2964
+ exports.assertIncreaseUnlockTime = assertIncreaseUnlockTime;
2965
+ exports.assertLiquidate = assertLiquidate;
2966
+ exports.assertLockDurationSeconds = assertLockDurationSeconds;
2967
+ exports.assertMerge = assertMerge;
2968
+ exports.assertSplitExtracted = assertSplitExtracted;
2969
+ exports.assertTransfer = assertTransfer;
2970
+ exports.assertUndelegateNotAlreadySelf = assertUndelegateNotAlreadySelf;
2971
+ exports.assertWithdraw = assertWithdraw;
2241
2972
  exports.bucketGovernorState = bucketGovernorState;
2242
2973
  exports.buildBravoProposalArgs = buildBravoProposalArgs;
2243
2974
  exports.buildDeltaProposalArgs = buildDeltaProposalArgs;
@@ -2263,6 +2994,7 @@ exports.buildEvmMultisignBodyContinuumDaoSplit = buildEvmMultisignBodyContinuumD
2263
2994
  exports.buildEvmMultisignBodyContinuumDaoTransfer = buildEvmMultisignBodyContinuumDaoTransfer;
2264
2995
  exports.buildEvmMultisignBodyContinuumDaoUndelegate = buildEvmMultisignBodyContinuumDaoUndelegate;
2265
2996
  exports.buildEvmMultisignBodyContinuumDaoWithdraw = buildEvmMultisignBodyContinuumDaoWithdraw;
2997
+ exports.computeLiquidationAmounts = computeLiquidationAmounts;
2266
2998
  exports.continuumDaoDeployment = continuumDaoDeployment;
2267
2999
  exports.continuumDaoExplainProposal = continuumDaoExplainProposal;
2268
3000
  exports.continuumDaoFetchDelegates = continuumDaoFetchDelegates;
@@ -2270,6 +3002,7 @@ exports.continuumDaoFetchLiveProposals = continuumDaoFetchLiveProposals;
2270
3002
  exports.continuumDaoFetchProposal = continuumDaoFetchProposal;
2271
3003
  exports.continuumDaoFetchProposalCount = continuumDaoFetchProposalCount;
2272
3004
  exports.continuumDaoFetchProposalState = continuumDaoFetchProposalState;
3005
+ exports.continuumDaoFetchProposalVoteEligibility = continuumDaoFetchProposalVoteEligibility;
2273
3006
  exports.continuumDaoFetchProposals = continuumDaoFetchProposals;
2274
3007
  exports.continuumDaoFetchVotingPower = continuumDaoFetchVotingPower;
2275
3008
  exports.continuumDaoForumCreateIdea = continuumDaoForumCreateIdea;
@@ -2311,6 +3044,7 @@ exports.encodeGovActionCalldata = encodeGovActionCalldata;
2311
3044
  exports.expectedForumSectionForProposalType = expectedForumSectionForProposalType;
2312
3045
  exports.explainGovAction = explainGovAction;
2313
3046
  exports.explainProposalRecord = explainProposalRecord;
3047
+ exports.floorUnlockTimeSeconds = floorUnlockTimeSeconds;
2314
3048
  exports.governorAddressOnEvmChain = governorAddressOnEvmChain;
2315
3049
  exports.isConfiguredAddress = isConfiguredAddress;
2316
3050
  exports.isContinuumDaoCtmOnAssetsChain = isContinuumDaoCtmOnAssetsChain;
@@ -2322,7 +3056,24 @@ exports.isForumIdeaSection = isForumIdeaSection;
2322
3056
  exports.isGovNoOpAction = isGovNoOpAction;
2323
3057
  exports.isVotingEscrowContinuumName = isVotingEscrowContinuumName;
2324
3058
  exports.listContinuumDaoDefaultAssets = listContinuumDaoDefaultAssets;
3059
+ exports.maxLockDurationSeconds = maxLockDurationSeconds;
3060
+ exports.preflightAttachVeCtm = preflightAttachVeCtm;
3061
+ exports.preflightCreateLock = preflightCreateLock;
3062
+ exports.preflightDelegate = preflightDelegate;
3063
+ exports.preflightIncreaseAmount = preflightIncreaseAmount;
3064
+ exports.preflightIncreaseUnlockTime = preflightIncreaseUnlockTime;
3065
+ exports.preflightLiquidate = preflightLiquidate;
3066
+ exports.preflightMerge = preflightMerge;
3067
+ exports.preflightRequestDetach = preflightRequestDetach;
3068
+ exports.preflightSplit = preflightSplit;
3069
+ exports.preflightTransfer = preflightTransfer;
3070
+ exports.preflightUndelegate = preflightUndelegate;
3071
+ exports.preflightWithdraw = preflightWithdraw;
2325
3072
  exports.proposalDescriptionHash = proposalDescriptionHash;
3073
+ exports.readCtmBalanceWei = readCtmBalanceWei;
3074
+ exports.readMinimumLockWei = readMinimumLockWei;
3075
+ exports.readVeCtmLockState = readVeCtmLockState;
3076
+ exports.resolveAttachGroupId = resolveAttachGroupId;
2326
3077
  exports.summarizeLiveProposals = summarizeLiveProposals;
2327
3078
  exports.votingEscrowAddressOnEvmChain = votingEscrowAddressOnEvmChain;
2328
3079
  //# sourceMappingURL=index.cjs.map