@sherwoodagent/cli 0.59.18 → 0.65.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.
package/dist/index.js CHANGED
@@ -171,7 +171,7 @@ import {
171
171
  import { config as loadDotenv } from "dotenv";
172
172
  import { createRequire } from "module";
173
173
  import { Command, Option } from "commander";
174
- import { parseUnits as parseUnits7, formatUnits as formatUnits5, isAddress as isAddress6 } from "viem";
174
+ import { parseUnits as parseUnits7, formatUnits as formatUnits6, isAddress as isAddress7 } from "viem";
175
175
  import chalk15 from "chalk";
176
176
  import ora8 from "ora";
177
177
  import { input, confirm, select } from "@inquirer/prompts";
@@ -211,7 +211,7 @@ var MoonwellProvider = class {
211
211
  };
212
212
 
213
213
  // src/commands/strategy-template.ts
214
- import { parseUnits, formatUnits, isAddress, erc20Abi, encodeAbiParameters as encodeAbiParameters9, decodeAbiParameters } from "viem";
214
+ import { parseUnits, formatUnits as formatUnits2, isAddress as isAddress2, erc20Abi, encodeAbiParameters as encodeAbiParameters11, decodeAbiParameters } from "viem";
215
215
  import chalk from "chalk";
216
216
  import ora from "ora";
217
217
  import { writeFileSync, mkdirSync } from "fs";
@@ -281,10 +281,1700 @@ function formatBatch(calls) {
281
281
  }).join("\n");
282
282
  }
283
283
 
284
+ // src/lib/calldata.ts
285
+ var _calldataOnly = false;
286
+ function setCalldataOnly(value) {
287
+ _calldataOnly = value;
288
+ }
289
+ function isCalldataOnly() {
290
+ return _calldataOnly;
291
+ }
292
+ function emitCalldata(action) {
293
+ process.stdout.write(JSON.stringify(action, null, 2) + "\n");
294
+ }
295
+
296
+ // ../sdk/dist/types.js
297
+ var CHAIN_IDS = {
298
+ BASE: 8453,
299
+ HYPEREVM: 999,
300
+ ROBINHOOD_L2: 46630
301
+ };
302
+ var SUPPORTED_CHAIN_IDS = [
303
+ CHAIN_IDS.BASE,
304
+ CHAIN_IDS.HYPEREVM,
305
+ CHAIN_IDS.ROBINHOOD_L2
306
+ ];
307
+
308
+ // ../sdk/dist/errors.js
309
+ var SdkError = class extends Error {
310
+ code;
311
+ cause;
312
+ constructor(code, message, cause) {
313
+ super(message);
314
+ this.code = code;
315
+ this.cause = cause;
316
+ this.name = "SdkError";
317
+ }
318
+ };
319
+ var usage = (message) => new SdkError("USAGE", message);
320
+ var unsupported = (message) => new SdkError("UNSUPPORTED", message);
321
+ var unavailable = (message, cause) => new SdkError("UNAVAILABLE", message, cause);
322
+
323
+ // ../sdk/dist/abis.js
324
+ var SYNDICATE_VAULT_ABI2 = [
325
+ // ERC-4626
326
+ {
327
+ name: "deposit",
328
+ type: "function",
329
+ stateMutability: "nonpayable",
330
+ inputs: [
331
+ { name: "assets", type: "uint256" },
332
+ { name: "receiver", type: "address" }
333
+ ],
334
+ outputs: [{ name: "shares", type: "uint256" }]
335
+ },
336
+ {
337
+ name: "redeem",
338
+ type: "function",
339
+ stateMutability: "nonpayable",
340
+ inputs: [
341
+ { name: "shares", type: "uint256" },
342
+ { name: "receiver", type: "address" },
343
+ { name: "owner", type: "address" }
344
+ ],
345
+ outputs: [{ name: "", type: "uint256" }]
346
+ },
347
+ {
348
+ name: "requestRedeem",
349
+ type: "function",
350
+ stateMutability: "nonpayable",
351
+ inputs: [
352
+ { name: "shares", type: "uint256" },
353
+ { name: "owner", type: "address" }
354
+ ],
355
+ outputs: [{ name: "requestId", type: "uint256" }]
356
+ },
357
+ // Reads
358
+ {
359
+ name: "asset",
360
+ type: "function",
361
+ stateMutability: "view",
362
+ inputs: [],
363
+ outputs: [{ name: "", type: "address" }]
364
+ },
365
+ {
366
+ name: "totalAssets",
367
+ type: "function",
368
+ stateMutability: "view",
369
+ inputs: [],
370
+ outputs: [{ name: "", type: "uint256" }]
371
+ },
372
+ {
373
+ name: "totalSupply",
374
+ type: "function",
375
+ stateMutability: "view",
376
+ inputs: [],
377
+ outputs: [{ name: "", type: "uint256" }]
378
+ },
379
+ {
380
+ name: "decimals",
381
+ type: "function",
382
+ stateMutability: "view",
383
+ inputs: [],
384
+ outputs: [{ name: "", type: "uint8" }]
385
+ },
386
+ {
387
+ name: "balanceOf",
388
+ type: "function",
389
+ stateMutability: "view",
390
+ inputs: [{ name: "account", type: "address" }],
391
+ outputs: [{ name: "", type: "uint256" }]
392
+ },
393
+ {
394
+ name: "owner",
395
+ type: "function",
396
+ stateMutability: "view",
397
+ inputs: [],
398
+ outputs: [{ name: "", type: "address" }]
399
+ },
400
+ {
401
+ name: "governor",
402
+ type: "function",
403
+ stateMutability: "view",
404
+ inputs: [],
405
+ outputs: [{ name: "", type: "address" }]
406
+ },
407
+ {
408
+ name: "redemptionsLocked",
409
+ type: "function",
410
+ stateMutability: "view",
411
+ inputs: [],
412
+ outputs: [{ name: "", type: "bool" }]
413
+ },
414
+ {
415
+ name: "paused",
416
+ type: "function",
417
+ stateMutability: "view",
418
+ inputs: [],
419
+ outputs: [{ name: "", type: "bool" }]
420
+ },
421
+ {
422
+ name: "openDeposits",
423
+ type: "function",
424
+ stateMutability: "view",
425
+ inputs: [],
426
+ outputs: [{ name: "", type: "bool" }]
427
+ },
428
+ {
429
+ name: "getAgentCount",
430
+ type: "function",
431
+ stateMutability: "view",
432
+ inputs: [],
433
+ outputs: [{ name: "", type: "uint256" }]
434
+ },
435
+ {
436
+ name: "isApprovedDepositor",
437
+ type: "function",
438
+ stateMutability: "view",
439
+ inputs: [{ name: "depositor", type: "address" }],
440
+ outputs: [{ name: "", type: "bool" }]
441
+ },
442
+ // Owner-side write surface
443
+ {
444
+ name: "approveDepositor",
445
+ type: "function",
446
+ stateMutability: "nonpayable",
447
+ inputs: [{ name: "depositor", type: "address" }],
448
+ outputs: []
449
+ },
450
+ {
451
+ name: "removeDepositor",
452
+ type: "function",
453
+ stateMutability: "nonpayable",
454
+ inputs: [{ name: "depositor", type: "address" }],
455
+ outputs: []
456
+ },
457
+ {
458
+ name: "registerAgent",
459
+ type: "function",
460
+ stateMutability: "nonpayable",
461
+ inputs: [
462
+ { name: "agentAddress", type: "address" },
463
+ { name: "agentId", type: "uint256" }
464
+ ],
465
+ outputs: []
466
+ }
467
+ ];
468
+ var SYNDICATE_GOVERNOR_ABI = [
469
+ // Propose (multi-arg). Order MUST match contract: vault, strategy,
470
+ // metadataURI, performanceFeeBps, strategyDuration, executeCalls,
471
+ // settlementCalls, coProposers.
472
+ {
473
+ name: "propose",
474
+ type: "function",
475
+ stateMutability: "nonpayable",
476
+ inputs: [
477
+ { name: "vault", type: "address" },
478
+ { name: "strategy", type: "address" },
479
+ { name: "metadataURI", type: "string" },
480
+ { name: "performanceFeeBps", type: "uint256" },
481
+ { name: "strategyDuration", type: "uint256" },
482
+ {
483
+ name: "executeCalls",
484
+ type: "tuple[]",
485
+ components: [
486
+ { name: "target", type: "address" },
487
+ { name: "data", type: "bytes" },
488
+ { name: "value", type: "uint256" }
489
+ ]
490
+ },
491
+ {
492
+ name: "settlementCalls",
493
+ type: "tuple[]",
494
+ components: [
495
+ { name: "target", type: "address" },
496
+ { name: "data", type: "bytes" },
497
+ { name: "value", type: "uint256" }
498
+ ]
499
+ },
500
+ {
501
+ name: "coProposers",
502
+ type: "tuple[]",
503
+ components: [
504
+ { name: "agent", type: "address" },
505
+ { name: "splitBps", type: "uint256" }
506
+ ]
507
+ }
508
+ ],
509
+ outputs: [{ name: "proposalId", type: "uint256" }]
510
+ },
511
+ // Lifecycle writes
512
+ {
513
+ name: "vote",
514
+ type: "function",
515
+ stateMutability: "nonpayable",
516
+ inputs: [
517
+ { name: "proposalId", type: "uint256" },
518
+ { name: "support", type: "uint8" }
519
+ ],
520
+ outputs: []
521
+ },
522
+ {
523
+ name: "executeProposal",
524
+ type: "function",
525
+ stateMutability: "nonpayable",
526
+ inputs: [{ name: "proposalId", type: "uint256" }],
527
+ outputs: []
528
+ },
529
+ {
530
+ name: "settleProposal",
531
+ type: "function",
532
+ stateMutability: "nonpayable",
533
+ inputs: [{ name: "proposalId", type: "uint256" }],
534
+ outputs: []
535
+ },
536
+ {
537
+ name: "cancelProposal",
538
+ type: "function",
539
+ stateMutability: "nonpayable",
540
+ inputs: [{ name: "proposalId", type: "uint256" }],
541
+ outputs: []
542
+ },
543
+ {
544
+ name: "vetoProposal",
545
+ type: "function",
546
+ stateMutability: "nonpayable",
547
+ inputs: [{ name: "proposalId", type: "uint256" }],
548
+ outputs: []
549
+ },
550
+ // Emergency settlement (GovernorEmergency, V1.5). `unstick` is the
551
+ // owner-instant pre-committed-calls path; `emergencySettleWithCalls` commits
552
+ // a new calldata hash and opens a guardian-reviewed window. The removed
553
+ // `emergencySettle` is intentionally absent — it is not on the deployed impl.
554
+ {
555
+ name: "unstick",
556
+ type: "function",
557
+ stateMutability: "nonpayable",
558
+ inputs: [{ name: "proposalId", type: "uint256" }],
559
+ outputs: []
560
+ },
561
+ {
562
+ name: "emergencySettleWithCalls",
563
+ type: "function",
564
+ stateMutability: "nonpayable",
565
+ inputs: [
566
+ { name: "proposalId", type: "uint256" },
567
+ {
568
+ name: "calls",
569
+ type: "tuple[]",
570
+ components: [
571
+ { name: "target", type: "address" },
572
+ { name: "data", type: "bytes" },
573
+ { name: "value", type: "uint256" }
574
+ ]
575
+ }
576
+ ],
577
+ outputs: []
578
+ },
579
+ // Parameter setters (GovernorParameters, V1.5 — owner-instant, no timelock).
580
+ { name: "setVotingPeriod", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
581
+ { name: "setExecutionWindow", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
582
+ { name: "setVetoThresholdBps", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
583
+ { name: "setMaxPerformanceFeeBps", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
584
+ { name: "setMaxStrategyDuration", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
585
+ { name: "setCooldownPeriod", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
586
+ { name: "setProtocolFeeBps", type: "function", stateMutability: "nonpayable", inputs: [{ name: "newValue", type: "uint256" }], outputs: [] },
587
+ // Reads
588
+ {
589
+ name: "getProposal",
590
+ type: "function",
591
+ stateMutability: "view",
592
+ inputs: [{ name: "proposalId", type: "uint256" }],
593
+ outputs: [
594
+ {
595
+ name: "",
596
+ type: "tuple",
597
+ components: [
598
+ { name: "id", type: "uint256" },
599
+ { name: "proposer", type: "address" },
600
+ { name: "vault", type: "address" },
601
+ { name: "strategy", type: "address" },
602
+ { name: "metadataURI", type: "string" },
603
+ { name: "performanceFeeBps", type: "uint256" },
604
+ { name: "strategyDuration", type: "uint256" },
605
+ { name: "votesFor", type: "uint256" },
606
+ { name: "votesAgainst", type: "uint256" },
607
+ { name: "votesAbstain", type: "uint256" },
608
+ { name: "snapshotTimestamp", type: "uint256" },
609
+ { name: "voteEnd", type: "uint256" },
610
+ { name: "reviewEnd", type: "uint256" },
611
+ { name: "executeBy", type: "uint256" },
612
+ { name: "executedAt", type: "uint256" },
613
+ { name: "state", type: "uint8" },
614
+ { name: "vetoThresholdBps", type: "uint256" }
615
+ ]
616
+ }
617
+ ]
618
+ },
619
+ {
620
+ name: "getProposalState",
621
+ type: "function",
622
+ stateMutability: "view",
623
+ inputs: [{ name: "proposalId", type: "uint256" }],
624
+ outputs: [{ name: "", type: "uint8" }]
625
+ },
626
+ {
627
+ name: "getActiveProposal",
628
+ type: "function",
629
+ stateMutability: "view",
630
+ inputs: [{ name: "vault", type: "address" }],
631
+ outputs: [{ name: "", type: "uint256" }]
632
+ },
633
+ {
634
+ name: "proposalCount",
635
+ type: "function",
636
+ stateMutability: "view",
637
+ inputs: [],
638
+ outputs: [{ name: "", type: "uint256" }]
639
+ },
640
+ {
641
+ name: "getGovernorParams",
642
+ type: "function",
643
+ stateMutability: "view",
644
+ inputs: [],
645
+ outputs: [
646
+ {
647
+ name: "",
648
+ type: "tuple",
649
+ components: [
650
+ { name: "votingPeriod", type: "uint256" },
651
+ { name: "executionWindow", type: "uint256" },
652
+ { name: "vetoThresholdBps", type: "uint256" },
653
+ { name: "maxPerformanceFeeBps", type: "uint256" },
654
+ { name: "cooldownPeriod", type: "uint256" },
655
+ { name: "collaborationWindow", type: "uint256" },
656
+ { name: "maxCoProposers", type: "uint256" },
657
+ { name: "minStrategyDuration", type: "uint256" },
658
+ { name: "maxStrategyDuration", type: "uint256" }
659
+ ]
660
+ }
661
+ ]
662
+ },
663
+ {
664
+ name: "protocolFeeBps",
665
+ type: "function",
666
+ stateMutability: "view",
667
+ inputs: [],
668
+ outputs: [{ name: "", type: "uint256" }]
669
+ },
670
+ {
671
+ name: "guardianFeeBps",
672
+ type: "function",
673
+ stateMutability: "view",
674
+ inputs: [],
675
+ outputs: [{ name: "", type: "uint256" }]
676
+ },
677
+ {
678
+ name: "protocolFeeRecipient",
679
+ type: "function",
680
+ stateMutability: "view",
681
+ inputs: [],
682
+ outputs: [{ name: "", type: "address" }]
683
+ }
684
+ ];
685
+ var SYNDICATE_FACTORY_ABI = [
686
+ {
687
+ name: "createSyndicate",
688
+ type: "function",
689
+ stateMutability: "nonpayable",
690
+ inputs: [
691
+ { name: "creatorAgentId", type: "uint256" },
692
+ {
693
+ name: "config",
694
+ type: "tuple",
695
+ components: [
696
+ { name: "metadataURI", type: "string" },
697
+ { name: "asset", type: "address" },
698
+ { name: "name", type: "string" },
699
+ { name: "symbol", type: "string" },
700
+ { name: "openDeposits", type: "bool" },
701
+ { name: "subdomain", type: "string" }
702
+ ]
703
+ }
704
+ ],
705
+ outputs: [
706
+ { name: "syndicateId", type: "uint256" },
707
+ { name: "vault", type: "address" }
708
+ ]
709
+ },
710
+ {
711
+ name: "syndicateCount",
712
+ type: "function",
713
+ stateMutability: "view",
714
+ inputs: [],
715
+ outputs: [{ name: "", type: "uint256" }]
716
+ },
717
+ {
718
+ name: "subdomainToSyndicate",
719
+ type: "function",
720
+ stateMutability: "view",
721
+ inputs: [{ name: "subdomain", type: "string" }],
722
+ outputs: [{ name: "", type: "uint256" }]
723
+ },
724
+ {
725
+ name: "vaultToSyndicate",
726
+ type: "function",
727
+ stateMutability: "view",
728
+ inputs: [{ name: "vault", type: "address" }],
729
+ outputs: [{ name: "", type: "uint256" }]
730
+ },
731
+ {
732
+ name: "syndicates",
733
+ type: "function",
734
+ stateMutability: "view",
735
+ inputs: [{ name: "id", type: "uint256" }],
736
+ outputs: [
737
+ { name: "id", type: "uint256" },
738
+ { name: "vault", type: "address" },
739
+ { name: "creator", type: "address" },
740
+ { name: "metadataURI", type: "string" },
741
+ { name: "createdAt", type: "uint256" },
742
+ { name: "active", type: "bool" },
743
+ { name: "subdomain", type: "string" }
744
+ ]
745
+ },
746
+ {
747
+ name: "getAllActiveSyndicates",
748
+ type: "function",
749
+ stateMutability: "view",
750
+ inputs: [],
751
+ outputs: [
752
+ {
753
+ name: "",
754
+ type: "tuple[]",
755
+ components: [
756
+ { name: "id", type: "uint256" },
757
+ { name: "vault", type: "address" },
758
+ { name: "creator", type: "address" },
759
+ { name: "metadataURI", type: "string" },
760
+ { name: "createdAt", type: "uint256" },
761
+ { name: "active", type: "bool" },
762
+ { name: "subdomain", type: "string" }
763
+ ]
764
+ }
765
+ ]
766
+ }
767
+ ];
768
+ var GUARDIAN_REGISTRY_ABI = [
769
+ // Guardian stake
770
+ {
771
+ name: "stakeAsGuardian",
772
+ type: "function",
773
+ stateMutability: "nonpayable",
774
+ inputs: [
775
+ { name: "amount", type: "uint256" },
776
+ { name: "agentId", type: "uint256" }
777
+ ],
778
+ outputs: []
779
+ },
780
+ {
781
+ name: "requestUnstakeGuardian",
782
+ type: "function",
783
+ stateMutability: "nonpayable",
784
+ inputs: [],
785
+ outputs: []
786
+ },
787
+ {
788
+ name: "cancelUnstakeGuardian",
789
+ type: "function",
790
+ stateMutability: "nonpayable",
791
+ inputs: [],
792
+ outputs: []
793
+ },
794
+ {
795
+ name: "claimUnstakeGuardian",
796
+ type: "function",
797
+ stateMutability: "nonpayable",
798
+ inputs: [],
799
+ outputs: []
800
+ },
801
+ // Delegation
802
+ {
803
+ name: "delegateStake",
804
+ type: "function",
805
+ stateMutability: "nonpayable",
806
+ inputs: [
807
+ { name: "delegate", type: "address" },
808
+ { name: "amount", type: "uint256" }
809
+ ],
810
+ outputs: []
811
+ },
812
+ {
813
+ name: "requestUnstakeDelegation",
814
+ type: "function",
815
+ stateMutability: "nonpayable",
816
+ inputs: [{ name: "delegate", type: "address" }],
817
+ outputs: []
818
+ },
819
+ {
820
+ name: "cancelUnstakeDelegation",
821
+ type: "function",
822
+ stateMutability: "nonpayable",
823
+ inputs: [{ name: "delegate", type: "address" }],
824
+ outputs: []
825
+ },
826
+ {
827
+ name: "claimUnstakeDelegation",
828
+ type: "function",
829
+ stateMutability: "nonpayable",
830
+ inputs: [{ name: "delegate", type: "address" }],
831
+ outputs: []
832
+ },
833
+ // Commission
834
+ {
835
+ name: "setCommission",
836
+ type: "function",
837
+ stateMutability: "nonpayable",
838
+ inputs: [{ name: "bps", type: "uint16" }],
839
+ outputs: []
840
+ },
841
+ // Claims
842
+ {
843
+ name: "claimProposalReward",
844
+ type: "function",
845
+ stateMutability: "nonpayable",
846
+ inputs: [{ name: "proposalId", type: "uint256" }],
847
+ outputs: []
848
+ },
849
+ {
850
+ name: "claimDelegatorProposalReward",
851
+ type: "function",
852
+ stateMutability: "nonpayable",
853
+ inputs: [
854
+ { name: "delegate", type: "address" },
855
+ { name: "proposalId", type: "uint256" }
856
+ ],
857
+ outputs: []
858
+ },
859
+ // Reads
860
+ {
861
+ name: "guardianStake",
862
+ type: "function",
863
+ stateMutability: "view",
864
+ inputs: [{ name: "guardian", type: "address" }],
865
+ outputs: [{ name: "", type: "uint256" }]
866
+ },
867
+ {
868
+ name: "isActiveGuardian",
869
+ type: "function",
870
+ stateMutability: "view",
871
+ inputs: [{ name: "guardian", type: "address" }],
872
+ outputs: [{ name: "", type: "bool" }]
873
+ }
874
+ ];
875
+ var ERC20_ABI2 = [
876
+ {
877
+ name: "approve",
878
+ type: "function",
879
+ stateMutability: "nonpayable",
880
+ inputs: [
881
+ { name: "spender", type: "address" },
882
+ { name: "amount", type: "uint256" }
883
+ ],
884
+ outputs: [{ name: "", type: "bool" }]
885
+ },
886
+ {
887
+ name: "allowance",
888
+ type: "function",
889
+ stateMutability: "view",
890
+ inputs: [
891
+ { name: "owner", type: "address" },
892
+ { name: "spender", type: "address" }
893
+ ],
894
+ outputs: [{ name: "", type: "uint256" }]
895
+ },
896
+ {
897
+ name: "balanceOf",
898
+ type: "function",
899
+ stateMutability: "view",
900
+ inputs: [{ name: "account", type: "address" }],
901
+ outputs: [{ name: "", type: "uint256" }]
902
+ },
903
+ {
904
+ name: "decimals",
905
+ type: "function",
906
+ stateMutability: "view",
907
+ inputs: [],
908
+ outputs: [{ name: "", type: "uint8" }]
909
+ },
910
+ {
911
+ name: "symbol",
912
+ type: "function",
913
+ stateMutability: "view",
914
+ inputs: [],
915
+ outputs: [{ name: "", type: "string" }]
916
+ }
917
+ ];
918
+ var IDENTITY_REGISTRY_ABI = [
919
+ {
920
+ name: "register",
921
+ type: "function",
922
+ stateMutability: "nonpayable",
923
+ inputs: [
924
+ { name: "agentURI", type: "string" },
925
+ {
926
+ name: "metadata",
927
+ type: "tuple[]",
928
+ components: [
929
+ { name: "metadataKey", type: "string" },
930
+ { name: "metadataValue", type: "bytes" }
931
+ ]
932
+ }
933
+ ],
934
+ outputs: [{ name: "agentId", type: "uint256" }]
935
+ }
936
+ ];
937
+ var EAS_ABI = [
938
+ {
939
+ name: "attest",
940
+ type: "function",
941
+ stateMutability: "payable",
942
+ inputs: [
943
+ {
944
+ name: "request",
945
+ type: "tuple",
946
+ components: [
947
+ { name: "schema", type: "bytes32" },
948
+ {
949
+ name: "data",
950
+ type: "tuple",
951
+ components: [
952
+ { name: "recipient", type: "address" },
953
+ { name: "expirationTime", type: "uint64" },
954
+ { name: "revocable", type: "bool" },
955
+ { name: "refUID", type: "bytes32" },
956
+ { name: "data", type: "bytes" },
957
+ { name: "value", type: "uint256" }
958
+ ]
959
+ }
960
+ ]
961
+ }
962
+ ],
963
+ outputs: [{ name: "", type: "bytes32" }]
964
+ }
965
+ ];
966
+ var STRATEGY_FACTORY_ABI = [
967
+ {
968
+ name: "cloneAndInit",
969
+ type: "function",
970
+ stateMutability: "nonpayable",
971
+ inputs: [
972
+ { name: "template", type: "address" },
973
+ { name: "vault", type: "address" },
974
+ { name: "proposer", type: "address" },
975
+ { name: "data", type: "bytes" }
976
+ ],
977
+ outputs: [{ name: "clone", type: "address" }]
978
+ },
979
+ {
980
+ name: "cloneAndInitDeterministic",
981
+ type: "function",
982
+ stateMutability: "nonpayable",
983
+ inputs: [
984
+ { name: "template", type: "address" },
985
+ { name: "vault", type: "address" },
986
+ { name: "proposer", type: "address" },
987
+ { name: "data", type: "bytes" },
988
+ { name: "salt", type: "bytes32" }
989
+ ],
990
+ outputs: [{ name: "clone", type: "address" }]
991
+ },
992
+ {
993
+ name: "syndicateFactory",
994
+ type: "function",
995
+ stateMutability: "view",
996
+ inputs: [],
997
+ outputs: [{ name: "", type: "address" }]
998
+ }
999
+ ];
1000
+
1001
+ // ../sdk/dist/addresses.js
1002
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1003
+ var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1004
+ var BASE_EAS = {
1005
+ chainId: CHAIN_IDS.BASE,
1006
+ address: "0x4200000000000000000000000000000000000021",
1007
+ schemas: {
1008
+ joinRequest: "0x1e7ce17b16233977ba913b156033e98f52029f4bee273a4abefe6c15ce11d5ef",
1009
+ agentApproved: "0x1013f7b38f433b2a93fc5ac162482813081c64edd67cea9b5a90698531ddb607"
1010
+ }
1011
+ };
1012
+ var NO_EAS = {
1013
+ chainId: CHAIN_IDS.BASE,
1014
+ address: ZERO_ADDRESS,
1015
+ schemas: { joinRequest: ZERO_BYTES32, agentApproved: ZERO_BYTES32 }
1016
+ };
1017
+ var BASE = {
1018
+ chainId: CHAIN_IDS.BASE,
1019
+ name: "Base",
1020
+ slug: "base",
1021
+ sherwood: {
1022
+ factory: "0xAC74EC56858d7F1f7618c8e77F65Fc26aDf33c82",
1023
+ governor: "0x9Fd3c87B34F254e3c5652A0394B9780c2F05d367",
1024
+ guardianRegistry: "0x49E4163b5e4b23F8f3d469Cf6fa197FB6b06A26E",
1025
+ vaultImpl: "0xfce4bcE08E9C047E4736f75C2B8557e2754Ce36A",
1026
+ batchExecutorLib: "0xbC79FbD5036C1Cc4A9d10BDf8628BF09a558496E",
1027
+ // Not yet deployed on Base — fill in `STRATEGY_FACTORY` from
1028
+ // contracts/chains/8453.json when it lands (issue #387).
1029
+ strategyFactory: ZERO_ADDRESS
1030
+ },
1031
+ identityRegistry: "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
1032
+ eas: BASE_EAS,
1033
+ tokens: {
1034
+ usdc: {
1035
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
1036
+ symbol: "USDC",
1037
+ decimals: 6
1038
+ },
1039
+ weth: {
1040
+ address: "0x4200000000000000000000000000000000000006",
1041
+ symbol: "WETH",
1042
+ decimals: 18
1043
+ }
1044
+ },
1045
+ explorer: "https://basescan.org",
1046
+ rpcs: ["https://mainnet.base.org"]
1047
+ };
1048
+ var HYPEREVM = {
1049
+ chainId: CHAIN_IDS.HYPEREVM,
1050
+ name: "HyperEVM",
1051
+ slug: "hyperevm",
1052
+ sherwood: {
1053
+ factory: "0xd05Ae0E8bcf13075C29817c805d6Cc14F214393a",
1054
+ governor: "0x67AD3D5F3d127Ef923Fd6f67b178633c408D3fd3",
1055
+ guardianRegistry: "0x8b5710EB4e2fA639F364Dcc3F3B30c8f12F460b9",
1056
+ vaultImpl: "0x2cbBe36Cf907A2BB410bacB0e4Fd632C7b012846",
1057
+ batchExecutorLib: "0x2c454bEF1b09c8a306a7058b8B510bF0DfF7179D",
1058
+ strategyFactory: ZERO_ADDRESS
1059
+ },
1060
+ // ERC-8004 identity is minted on Base; HyperEVM syndicates reference the
1061
+ // Base-minted token id. Coordination attestations are pinned to Base EAS.
1062
+ identityRegistry: ZERO_ADDRESS,
1063
+ eas: BASE_EAS,
1064
+ tokens: {
1065
+ usdc: {
1066
+ address: "0xb88339CB7199b77E23DB6E890353E22632Ba630f",
1067
+ symbol: "USDC",
1068
+ decimals: 6
1069
+ }
1070
+ },
1071
+ explorer: "https://hyperliquid.cloud.blockscout.com",
1072
+ rpcs: ["https://rpc.hyperliquid.xyz/evm"]
1073
+ };
1074
+ var ROBINHOOD_L2 = {
1075
+ chainId: CHAIN_IDS.ROBINHOOD_L2,
1076
+ name: "Robinhood L2 Testnet",
1077
+ slug: "robinhood-l2",
1078
+ sherwood: {
1079
+ factory: "0x6d026e2f5Ff0C34A01690EC46Cb601B8fF391985",
1080
+ governor: "0xd882056ba6b0aEd8908c541884B327121E2f2C9C",
1081
+ // Robinhood L2 doesn't have a guardian registry yet; use zero so callers
1082
+ // get a clear UNSUPPORTED rather than a confusing revert downstream.
1083
+ guardianRegistry: "0x0000000000000000000000000000000000000000",
1084
+ vaultImpl: "0xF4720523325f9A4546F43391484DCd1D28dFc266",
1085
+ batchExecutorLib: "0x1493f5a7E5d82e1e56c34e2Ba300f56F97186017",
1086
+ strategyFactory: ZERO_ADDRESS
1087
+ },
1088
+ // No ERC-8004 registry and no EAS path on Robinhood L2.
1089
+ identityRegistry: ZERO_ADDRESS,
1090
+ eas: NO_EAS,
1091
+ tokens: {},
1092
+ explorer: "https://explorer-robinhood-l2.t.conduit.xyz",
1093
+ rpcs: ["https://rpc-robinhood-l2.t.conduit.xyz"]
1094
+ };
1095
+ var REGISTRY = {
1096
+ [CHAIN_IDS.BASE]: BASE,
1097
+ [CHAIN_IDS.HYPEREVM]: HYPEREVM,
1098
+ [CHAIN_IDS.ROBINHOOD_L2]: ROBINHOOD_L2
1099
+ };
1100
+ function getDeployment(chainId) {
1101
+ const entry = REGISTRY[chainId];
1102
+ if (!entry)
1103
+ throw unsupported(`No Sherwood deployment for chain ${chainId}`);
1104
+ return entry;
1105
+ }
1106
+
1107
+ // ../sdk/dist/encoders/vault.js
1108
+ import { encodeFunctionData, getAddress, maxUint256 } from "viem";
1109
+ var WETH_ABI = [
1110
+ {
1111
+ name: "deposit",
1112
+ type: "function",
1113
+ stateMutability: "payable",
1114
+ inputs: [],
1115
+ outputs: []
1116
+ }
1117
+ ];
1118
+ var ZERO_VALUE = "0x0";
1119
+ function toHexValue(value) {
1120
+ return `0x${value.toString(16)}`;
1121
+ }
1122
+ function encodeApproveTx(token, spender, amount, chainId) {
1123
+ return {
1124
+ to: token,
1125
+ data: encodeFunctionData({
1126
+ abi: ERC20_ABI2,
1127
+ functionName: "approve",
1128
+ args: [spender, amount]
1129
+ }),
1130
+ value: ZERO_VALUE,
1131
+ chainId
1132
+ };
1133
+ }
1134
+ function encodeDepositTx(vault, assets, receiver, chainId) {
1135
+ return {
1136
+ to: vault,
1137
+ data: encodeFunctionData({
1138
+ abi: SYNDICATE_VAULT_ABI2,
1139
+ functionName: "deposit",
1140
+ args: [assets, receiver]
1141
+ }),
1142
+ value: ZERO_VALUE,
1143
+ chainId
1144
+ };
1145
+ }
1146
+ function encodeDeposit(args, chainId) {
1147
+ if (args.assets <= 0n) {
1148
+ throw usage("`assets` must be > 0");
1149
+ }
1150
+ const vault = getAddress(args.vault);
1151
+ const receiver = getAddress(args.receiver);
1152
+ const asset = getAddress(args.asset);
1153
+ const txs = [];
1154
+ let wrapped = false;
1155
+ if (args.wrapEth) {
1156
+ const weth = getDeployment(chainId).tokens.weth;
1157
+ if (!weth || getAddress(weth.address) !== asset) {
1158
+ throw usage("`wrapEth` is only valid for WETH vaults \u2014 the vault asset must be the chain's WETH.");
1159
+ }
1160
+ const held = args.currentWethBalance ?? 0n;
1161
+ const shortfall = held >= args.assets ? 0n : args.assets - held;
1162
+ if (shortfall > 0n) {
1163
+ txs.push({
1164
+ to: asset,
1165
+ data: encodeFunctionData({ abi: WETH_ABI, functionName: "deposit", args: [] }),
1166
+ value: toHexValue(shortfall),
1167
+ chainId
1168
+ });
1169
+ wrapped = true;
1170
+ }
1171
+ }
1172
+ const needsApprove = args.currentAllowance === void 0 || args.currentAllowance < args.assets;
1173
+ if (needsApprove) {
1174
+ txs.push(encodeApproveTx(asset, vault, maxUint256, chainId));
1175
+ }
1176
+ txs.push(encodeDepositTx(vault, args.assets, receiver, chainId));
1177
+ const minDecimal = formatUnits(args.assets, args.assetDecimals);
1178
+ let description;
1179
+ if (wrapped) {
1180
+ description = needsApprove ? `Wrap ETH\u2192WETH, approve ${args.assetSymbol} for ${vault}, then deposit ${minDecimal} ${args.assetSymbol}.` : `Wrap ETH\u2192WETH, then deposit ${minDecimal} ${args.assetSymbol}.`;
1181
+ } else {
1182
+ description = needsApprove ? `Approve ${args.assetSymbol} for ${vault}, then deposit ${minDecimal} ${args.assetSymbol}.` : `Deposit ${minDecimal} ${args.assetSymbol} into ${vault}.`;
1183
+ }
1184
+ return {
1185
+ txs,
1186
+ preconditions: [
1187
+ {
1188
+ type: "balance",
1189
+ asset,
1190
+ assetSymbol: args.assetSymbol,
1191
+ min: args.assets.toString(),
1192
+ minDecimal
1193
+ },
1194
+ {
1195
+ type: "allowance",
1196
+ token: asset,
1197
+ spender: vault,
1198
+ min: args.assets.toString(),
1199
+ minDecimal
1200
+ },
1201
+ { type: "vault-not-locked", vault },
1202
+ { type: "depositor-approved", vault, depositor: receiver }
1203
+ ],
1204
+ description,
1205
+ note: "Both txs are gated by vault state. If `redemptionsLocked()` is true the deposit reverts unless live NAV is available; check the vault read endpoint before broadcasting."
1206
+ };
1207
+ }
1208
+ function formatUnits(value, decimals) {
1209
+ if (decimals === 0)
1210
+ return value.toString();
1211
+ const negative = value < 0n;
1212
+ const abs = negative ? -value : value;
1213
+ const str = abs.toString().padStart(decimals + 1, "0");
1214
+ const head = str.slice(0, str.length - decimals);
1215
+ const tail = str.slice(str.length - decimals).replace(/0+$/, "");
1216
+ const out = tail.length > 0 ? `${head}.${tail}` : head;
1217
+ return negative ? `-${out}` : out;
1218
+ }
1219
+ function encodeRedeem(args, chainId) {
1220
+ if (args.shares <= 0n)
1221
+ throw usage("`shares` must be > 0");
1222
+ const vault = getAddress(args.vault);
1223
+ const receiver = getAddress(args.receiver);
1224
+ const owner = getAddress(args.owner);
1225
+ return {
1226
+ txs: [
1227
+ {
1228
+ to: vault,
1229
+ data: encodeFunctionData({
1230
+ abi: SYNDICATE_VAULT_ABI2,
1231
+ functionName: "redeem",
1232
+ args: [args.shares, receiver, owner]
1233
+ }),
1234
+ value: ZERO_VALUE,
1235
+ chainId
1236
+ }
1237
+ ],
1238
+ preconditions: [
1239
+ { type: "vault-not-locked", vault }
1240
+ ],
1241
+ description: `Redeem ${args.shares.toString()} shares of ${vault} \u2192 ${receiver}.`,
1242
+ note: "Synchronous path. Reverts if the vault has an active proposal AND live NAV is unavailable; in that case use /prepare/request-redeem (queue path) instead."
1243
+ };
1244
+ }
1245
+ function encodeApproveDepositor(args, chainId) {
1246
+ const vault = getAddress(args.vault);
1247
+ const depositor = getAddress(args.depositor);
1248
+ return {
1249
+ txs: [
1250
+ {
1251
+ to: vault,
1252
+ data: encodeFunctionData({
1253
+ abi: SYNDICATE_VAULT_ABI2,
1254
+ functionName: "approveDepositor",
1255
+ args: [depositor]
1256
+ }),
1257
+ value: ZERO_VALUE,
1258
+ chainId
1259
+ }
1260
+ ],
1261
+ preconditions: [],
1262
+ description: `Approve depositor ${depositor} on vault ${vault}.`,
1263
+ note: "Vault owner only."
1264
+ };
1265
+ }
1266
+ function encodeRegisterAgent(args, chainId) {
1267
+ const vault = getAddress(args.vault);
1268
+ const agentAddress = getAddress(args.agentAddress);
1269
+ const id = typeof args.agentId === "bigint" ? args.agentId : BigInt(args.agentId);
1270
+ if (id < 0n)
1271
+ throw usage("`agentId` must be a non-negative integer");
1272
+ return {
1273
+ txs: [
1274
+ {
1275
+ to: vault,
1276
+ data: encodeFunctionData({
1277
+ abi: SYNDICATE_VAULT_ABI2,
1278
+ functionName: "registerAgent",
1279
+ args: [agentAddress, id]
1280
+ }),
1281
+ value: ZERO_VALUE,
1282
+ chainId
1283
+ }
1284
+ ],
1285
+ preconditions: [],
1286
+ description: `Register agent ${agentAddress} (id ${id.toString()}) on vault ${vault}.`,
1287
+ note: "Vault owner only. The ERC-8004 NFT must be owned by `agentAddress` or by the vault owner at registration time."
1288
+ };
1289
+ }
1290
+
1291
+ // ../sdk/dist/encoders/governor.js
1292
+ import { encodeFunctionData as encodeFunctionData2, getAddress as getAddress2, isAddress } from "viem";
1293
+ var ZERO_VALUE2 = "0x0";
1294
+ var VOTE_TYPES = {
1295
+ For: 0,
1296
+ Against: 1,
1297
+ Abstain: 2
1298
+ };
1299
+ function governorTx(chainId, data) {
1300
+ return {
1301
+ to: getDeployment(chainId).sherwood.governor,
1302
+ data,
1303
+ value: ZERO_VALUE2,
1304
+ chainId
1305
+ };
1306
+ }
1307
+ function parseProposalId(value) {
1308
+ if (typeof value === "bigint") {
1309
+ if (value < 0n)
1310
+ throw usage("`proposalId` must be a non-negative integer");
1311
+ return value;
1312
+ }
1313
+ if (typeof value === "number") {
1314
+ if (!Number.isInteger(value) || value < 0) {
1315
+ throw usage("`proposalId` must be a non-negative integer");
1316
+ }
1317
+ return BigInt(value);
1318
+ }
1319
+ if (!/^\d+$/.test(value)) {
1320
+ throw usage(`\`proposalId\` must be a non-negative integer string, got "${value}"`);
1321
+ }
1322
+ return BigInt(value);
1323
+ }
1324
+ function encodeVote(args, chainId) {
1325
+ const support = VOTE_TYPES[args.vote];
1326
+ if (support === void 0) {
1327
+ throw usage(`Unsupported vote "${args.vote}" \u2014 must be one of: ${Object.keys(VOTE_TYPES).join(", ")}`);
1328
+ }
1329
+ const proposalId = parseProposalId(args.proposalId);
1330
+ const data = encodeFunctionData2({
1331
+ abi: SYNDICATE_GOVERNOR_ABI,
1332
+ functionName: "vote",
1333
+ args: [proposalId, support]
1334
+ });
1335
+ return {
1336
+ txs: [governorTx(chainId, data)],
1337
+ preconditions: [],
1338
+ description: `Vote ${args.vote} on proposal ${proposalId.toString()}.`,
1339
+ note: "Voter must hold ERC20Votes shares of the vault at the proposal's snapshot timestamp; double-voting reverts. Use the proposal read endpoint to check `voteEnd` first."
1340
+ };
1341
+ }
1342
+ function lifecycleAction(fn, proposalId, chainId, description, note) {
1343
+ const data = encodeFunctionData2({
1344
+ abi: SYNDICATE_GOVERNOR_ABI,
1345
+ functionName: fn,
1346
+ args: [proposalId]
1347
+ });
1348
+ return {
1349
+ txs: [governorTx(chainId, data)],
1350
+ preconditions: [],
1351
+ description,
1352
+ note
1353
+ };
1354
+ }
1355
+ function encodeExecute(args, chainId) {
1356
+ const id = parseProposalId(args.proposalId);
1357
+ return lifecycleAction("executeProposal", id, chainId, `Execute proposal ${id.toString()} (open the strategy).`, "Reverts if the proposal is not in the `Approved` state. Check `state == Approved` via the proposal read endpoint first.");
1358
+ }
1359
+ function encodeSettle(args, chainId) {
1360
+ const id = parseProposalId(args.proposalId);
1361
+ return lifecycleAction("settleProposal", id, chainId, `Settle proposal ${id.toString()} (close the strategy and distribute fees).`, "Proposer can settle anytime; anyone can settle after the strategy duration elapses.");
1362
+ }
1363
+ function encodeCancel(args, chainId) {
1364
+ const id = parseProposalId(args.proposalId);
1365
+ return lifecycleAction("cancelProposal", id, chainId, `Cancel proposal ${id.toString()} (proposer-only).`, "Only the proposer can cancel, and only while the proposal is in `Pending`, `GuardianReview`, or `Approved`.");
1366
+ }
1367
+ var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
1368
+ function validateBatchCall(call, scope, idx) {
1369
+ if (!call || typeof call !== "object") {
1370
+ throw usage(`\`${scope}[${idx}]\` must be an object`);
1371
+ }
1372
+ const c = call;
1373
+ if (typeof c.target !== "string" || !isAddress(c.target)) {
1374
+ throw usage(`\`${scope}[${idx}].target\` must be a 0x-prefixed address`);
1375
+ }
1376
+ if (typeof c.data !== "string" || !/^0x[0-9a-fA-F]*$/.test(c.data)) {
1377
+ throw usage(`\`${scope}[${idx}].data\` must be a 0x-prefixed hex string`);
1378
+ }
1379
+ let value;
1380
+ if (typeof c.value === "string") {
1381
+ if (!/^\d+$/.test(c.value)) {
1382
+ throw usage(`\`${scope}[${idx}].value\` must be a non-negative integer string`);
1383
+ }
1384
+ value = BigInt(c.value);
1385
+ } else if (typeof c.value === "bigint") {
1386
+ if (c.value < 0n) {
1387
+ throw usage(`\`${scope}[${idx}].value\` must be non-negative`);
1388
+ }
1389
+ value = c.value;
1390
+ } else if (c.value === void 0 || c.value === null) {
1391
+ value = 0n;
1392
+ } else {
1393
+ throw usage(`\`${scope}[${idx}].value\` must be a non-negative integer string (or omitted for 0)`);
1394
+ }
1395
+ return { target: getAddress2(c.target), data: c.data, value };
1396
+ }
1397
+ function encodePropose(args, chainId) {
1398
+ const vault = getAddress2(args.vault);
1399
+ const strategy2 = args.strategy === ZERO_ADDRESS2 ? ZERO_ADDRESS2 : getAddress2(args.strategy);
1400
+ if (typeof args.metadataURI !== "string" || args.metadataURI.length === 0) {
1401
+ throw usage("`metadataURI` must be a non-empty string (e.g. ipfs://...)");
1402
+ }
1403
+ if (!Number.isInteger(args.performanceFeeBps) || args.performanceFeeBps < 0 || args.performanceFeeBps > 1e4) {
1404
+ throw usage("`performanceFeeBps` must be an integer in [0, 10000]");
1405
+ }
1406
+ const strategyDuration = typeof args.strategyDuration === "bigint" ? args.strategyDuration : BigInt(args.strategyDuration);
1407
+ if (strategyDuration <= 0n) {
1408
+ throw usage("`strategyDuration` must be > 0 (seconds)");
1409
+ }
1410
+ if (!Array.isArray(args.executeCalls)) {
1411
+ throw usage("`executeCalls` must be an array of {target, data, value} objects");
1412
+ }
1413
+ if (!Array.isArray(args.settlementCalls)) {
1414
+ throw usage("`settlementCalls` must be an array of {target, data, value} objects");
1415
+ }
1416
+ const executeCalls = args.executeCalls.map((c, i) => validateBatchCall(c, "executeCalls", i));
1417
+ const settlementCalls = args.settlementCalls.map((c, i) => validateBatchCall(c, "settlementCalls", i));
1418
+ const coProposers = (args.coProposers ?? []).map((cp, i) => {
1419
+ if (!cp || typeof cp !== "object") {
1420
+ throw usage(`\`coProposers[${i}]\` must be an object`);
1421
+ }
1422
+ const agent = cp.agent;
1423
+ const splitBps = cp.splitBps;
1424
+ if (typeof agent !== "string" || !isAddress(agent)) {
1425
+ throw usage(`\`coProposers[${i}].agent\` must be a 0x-prefixed address`);
1426
+ }
1427
+ let bps;
1428
+ if (typeof splitBps === "string" && /^\d+$/.test(splitBps)) {
1429
+ bps = BigInt(splitBps);
1430
+ } else if (typeof splitBps === "bigint") {
1431
+ bps = splitBps;
1432
+ } else if (typeof splitBps === "number" && Number.isInteger(splitBps) && splitBps >= 0) {
1433
+ bps = BigInt(splitBps);
1434
+ } else {
1435
+ throw usage(`\`coProposers[${i}].splitBps\` must be a non-negative integer (string, number, or bigint)`);
1436
+ }
1437
+ if (bps < 0n || bps > 10000n) {
1438
+ throw usage(`\`coProposers[${i}].splitBps\` must be in [0, 10000]`);
1439
+ }
1440
+ return { agent: getAddress2(agent), splitBps: bps };
1441
+ });
1442
+ const data = encodeFunctionData2({
1443
+ abi: SYNDICATE_GOVERNOR_ABI,
1444
+ functionName: "propose",
1445
+ args: [
1446
+ vault,
1447
+ strategy2,
1448
+ args.metadataURI,
1449
+ BigInt(args.performanceFeeBps),
1450
+ strategyDuration,
1451
+ executeCalls,
1452
+ settlementCalls,
1453
+ coProposers
1454
+ ]
1455
+ });
1456
+ return {
1457
+ txs: [governorTx(chainId, data)],
1458
+ preconditions: [
1459
+ { type: "vault-not-locked", vault }
1460
+ ],
1461
+ description: `Propose a strategy on ${vault} (duration ${strategyDuration.toString()}s, fee ${args.performanceFeeBps} bps).`,
1462
+ note: "Reverts if the vault has an active proposal (cooldown not elapsed). The strategy address is immutable post-propose; pass address(0) to opt out of live NAV. `coProposers[].splitBps` total must satisfy contract-side constraints."
1463
+ };
1464
+ }
1465
+ function encodeUnstick(args, chainId) {
1466
+ const id = parseProposalId(args.proposalId);
1467
+ const data = encodeFunctionData2({
1468
+ abi: SYNDICATE_GOVERNOR_ABI,
1469
+ functionName: "unstick",
1470
+ args: [id]
1471
+ });
1472
+ return {
1473
+ txs: [governorTx(chainId, data)],
1474
+ preconditions: [],
1475
+ description: `Unstick proposal ${id.toString()} \u2014 run its governance-approved settlement calls (owner-instant, no funds beyond the approved calls).`,
1476
+ note: "Vault-owner only. Valid only when the proposal is Executed AND its strategy duration has elapsed. Runs the proposal's pre-committed settlement calls and finalizes settlement; no owner stake required. Reverts if those calls revert \u2014 if they can't succeed, open an emergency-settle with custom unwind calls instead."
1477
+ };
1478
+ }
1479
+ function encodeEmergencySettle(args, chainId) {
1480
+ const id = parseProposalId(args.proposalId);
1481
+ if (!Array.isArray(args.calls)) {
1482
+ throw usage("`calls` must be an array of {target, data, value} objects");
1483
+ }
1484
+ const calls = args.calls.map((c, i) => validateBatchCall(c, "calls", i));
1485
+ const data = encodeFunctionData2({
1486
+ abi: SYNDICATE_GOVERNOR_ABI,
1487
+ functionName: "emergencySettleWithCalls",
1488
+ args: [id, calls]
1489
+ });
1490
+ return {
1491
+ txs: [governorTx(chainId, data)],
1492
+ preconditions: [],
1493
+ description: `Open an emergency-settle review on proposal ${id.toString()} with ${calls.length} unwind call(s).`,
1494
+ note: "Vault-owner only, requires bonded owner stake. Does NOT execute immediately \u2014 it commits the calls' hash and opens a guardian-reviewed window (default ~24h). After the window, finalize with finalizeEmergencySettle; guardians can block within it (owner stake burned on block-quorum)."
1495
+ };
1496
+ }
1497
+ function parseUintArg(value, label) {
1498
+ if (typeof value === "bigint") {
1499
+ if (value < 0n)
1500
+ throw usage(`\`${label}\` must be a non-negative integer`);
1501
+ return value;
1502
+ }
1503
+ if (typeof value === "number") {
1504
+ if (!Number.isInteger(value) || value < 0) {
1505
+ throw usage(`\`${label}\` must be a non-negative integer`);
1506
+ }
1507
+ return BigInt(value);
1508
+ }
1509
+ if (!/^\d+$/.test(value)) {
1510
+ throw usage(`\`${label}\` must be a non-negative integer string, got "${value}"`);
1511
+ }
1512
+ return BigInt(value);
1513
+ }
1514
+ var GOVERNOR_PARAM_SETTERS = {
1515
+ votingPeriod: "setVotingPeriod",
1516
+ executionWindow: "setExecutionWindow",
1517
+ vetoThresholdBps: "setVetoThresholdBps",
1518
+ maxPerformanceFeeBps: "setMaxPerformanceFeeBps",
1519
+ maxStrategyDuration: "setMaxStrategyDuration",
1520
+ cooldownPeriod: "setCooldownPeriod",
1521
+ protocolFeeBps: "setProtocolFeeBps"
1522
+ };
1523
+ function encodeGovernorParam(args, chainId) {
1524
+ const fn = GOVERNOR_PARAM_SETTERS[args.param];
1525
+ if (!fn) {
1526
+ throw usage(`Unknown governor param "${args.param}" \u2014 must be one of: ${Object.keys(GOVERNOR_PARAM_SETTERS).join(", ")}`);
1527
+ }
1528
+ const value = parseUintArg(args.value, args.param);
1529
+ const data = encodeFunctionData2({
1530
+ abi: SYNDICATE_GOVERNOR_ABI,
1531
+ functionName: fn,
1532
+ args: [value]
1533
+ });
1534
+ return {
1535
+ txs: [governorTx(chainId, data)],
1536
+ preconditions: [],
1537
+ description: `Set governor parameter ${args.param} = ${value.toString()}.`,
1538
+ note: "Owner-only (the owner multisig). V1.5 has no on-chain parameter timelock \u2014 the change applies immediately on this call. Bounds are validated on-chain; out-of-range values revert. Setting a non-zero protocolFeeBps reverts unless protocolFeeRecipient is already set."
1539
+ };
1540
+ }
1541
+
1542
+ // ../sdk/dist/encoders/factory.js
1543
+ import { encodeFunctionData as encodeFunctionData3, getAddress as getAddress3 } from "viem";
1544
+ var ZERO_VALUE3 = "0x0";
1545
+ var SUBDOMAIN_RE = /^[a-z0-9-]{1,63}$/;
1546
+ function encodeCreateSyndicate(args, chainId) {
1547
+ if (typeof args.metadataURI !== "string" || args.metadataURI.length === 0) {
1548
+ throw usage("`metadataURI` must be a non-empty string (e.g. ipfs://...)");
1549
+ }
1550
+ if (typeof args.name !== "string" || args.name.length === 0) {
1551
+ throw usage("`name` must be a non-empty string");
1552
+ }
1553
+ if (typeof args.symbol !== "string" || args.symbol.length === 0) {
1554
+ throw usage("`symbol` must be a non-empty string");
1555
+ }
1556
+ if (typeof args.openDeposits !== "boolean") {
1557
+ throw usage("`openDeposits` must be a boolean");
1558
+ }
1559
+ if (!SUBDOMAIN_RE.test(args.subdomain)) {
1560
+ throw usage("`subdomain` must match /^[a-z0-9-]{1,63}$/ (lowercase letters, digits, hyphens)");
1561
+ }
1562
+ const asset = getAddress3(args.asset);
1563
+ const creatorAgentId = typeof args.creatorAgentId === "bigint" ? args.creatorAgentId : BigInt(args.creatorAgentId);
1564
+ if (creatorAgentId < 0n)
1565
+ throw usage("`creatorAgentId` must be non-negative");
1566
+ const factory = getDeployment(chainId).sherwood.factory;
1567
+ const data = encodeFunctionData3({
1568
+ abi: SYNDICATE_FACTORY_ABI,
1569
+ functionName: "createSyndicate",
1570
+ args: [
1571
+ creatorAgentId,
1572
+ {
1573
+ metadataURI: args.metadataURI,
1574
+ asset,
1575
+ name: args.name,
1576
+ symbol: args.symbol,
1577
+ openDeposits: args.openDeposits,
1578
+ subdomain: args.subdomain
1579
+ }
1580
+ ]
1581
+ });
1582
+ return {
1583
+ txs: [{ to: factory, data, value: ZERO_VALUE3, chainId }],
1584
+ preconditions: [
1585
+ // Creator must own the ERC-8004 NFT for `creatorAgentId` and have
1586
+ // pre-staked the owner bond via guardianRegistry.prepareOwnerStake.
1587
+ // No on-chain check from the SDK side — agent verifies upstream.
1588
+ ],
1589
+ description: `Create syndicate "${args.subdomain}" with ${args.openDeposits ? "open" : "whitelisted"} deposits.`,
1590
+ note: "Caller must own the ERC-8004 NFT for `creatorAgentId` and must have called `guardianRegistry.prepareOwnerStake` first to bond the owner stake. The factory's `createSyndicate` deploys a vault proxy + binds the owner stake atomically."
1591
+ };
1592
+ }
1593
+
1594
+ // ../sdk/dist/encoders/strategy.js
1595
+ import { concatHex, encodeAbiParameters, encodeFunctionData as encodeFunctionData4, encodePacked, getAddress as getAddress4, getCreate2Address, isHex, keccak256 } from "viem";
1596
+ var ZERO_ADDRESS3 = "0x0000000000000000000000000000000000000000";
1597
+ var ZERO_VALUE4 = "0x0";
1598
+ var ERC1167_PREFIX = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73";
1599
+ var ERC1167_SUFFIX = "0x5af43d82803e903d91602b57fd5bf3";
1600
+ function predictStrategyCloneAddress(factory, template, salt) {
1601
+ const initCode = concatHex([
1602
+ ERC1167_PREFIX,
1603
+ getAddress4(template),
1604
+ ERC1167_SUFFIX
1605
+ ]);
1606
+ return getCreate2Address({
1607
+ from: getAddress4(factory),
1608
+ salt,
1609
+ bytecode: initCode
1610
+ });
1611
+ }
1612
+ function deriveStrategySalt(vault, proposer, template, initData) {
1613
+ return keccak256(encodePacked(["address", "address", "address", "bytes"], [getAddress4(vault), getAddress4(proposer), getAddress4(template), initData]));
1614
+ }
1615
+ function effectiveStrategySalt(vault, salt) {
1616
+ return keccak256(encodeAbiParameters([{ type: "address" }, { type: "bytes32" }], [getAddress4(vault), salt]));
1617
+ }
1618
+ function requireSalt(value) {
1619
+ if (typeof value !== "string" || !isHex(value) || value.length !== 66) {
1620
+ throw usage("`salt` must be a 0x-prefixed 32-byte hex string (66 chars)");
1621
+ }
1622
+ return value;
1623
+ }
1624
+ function encodeStrategyDeploy(args, chainId) {
1625
+ const template = getAddress4(args.template);
1626
+ const vault = getAddress4(args.vault);
1627
+ const proposer = getAddress4(args.proposer);
1628
+ if (typeof args.initData !== "string" || !isHex(args.initData)) {
1629
+ throw usage("`initData` must be a 0x-prefixed hex string");
1630
+ }
1631
+ if (template === ZERO_ADDRESS3) {
1632
+ throw usage("`template` must be a non-zero address");
1633
+ }
1634
+ if (proposer === ZERO_ADDRESS3) {
1635
+ throw usage("`proposer` must be a non-zero address");
1636
+ }
1637
+ const factory = args.factory ? getAddress4(args.factory) : getDeployment(chainId).sherwood.strategyFactory;
1638
+ if (factory === ZERO_ADDRESS3) {
1639
+ throw unavailable(`StrategyFactory is not yet deployed on chain ${chainId} \u2014 mirror its address into the SDK once it lands (issue #387), or pass an explicit \`factory\`.`);
1640
+ }
1641
+ const salt = args.salt ? requireSalt(args.salt) : deriveStrategySalt(vault, proposer, template, args.initData);
1642
+ const clone = predictStrategyCloneAddress(factory, template, effectiveStrategySalt(vault, salt));
1643
+ const data = encodeFunctionData4({
1644
+ abi: STRATEGY_FACTORY_ABI,
1645
+ functionName: "cloneAndInitDeterministic",
1646
+ args: [template, vault, proposer, args.initData, salt]
1647
+ });
1648
+ const tx = { to: factory, data, value: ZERO_VALUE4, chainId };
1649
+ return {
1650
+ txs: [tx],
1651
+ preconditions: [],
1652
+ clone,
1653
+ salt,
1654
+ factory,
1655
+ description: `Deploy a strategy clone of ${template} for vault ${vault} (deterministic \u2014 clone at ${clone}).`,
1656
+ note: "Broadcast this tx from `proposer` \u2014 the factory requires proposer == msg.sender, and msg.sender must be the vault owner or a registered agent. The vault must be registered on the SyndicateFactory and `template` must be on the factory's allowlist (the factory reverts otherwise). The clone address is fixed by (factory, template, vault, salt) \u2014 reference it as the proposal `strategy` and inside executeCalls/settlementCalls. The factory binds the salt to the vault, and `_authClone` lets only the vault's owner/agents deploy there, so the predicted address can't be front-run."
1657
+ };
1658
+ }
1659
+
1660
+ // ../sdk/dist/encoders/identity.js
1661
+ import { encodeFunctionData as encodeFunctionData5, getAddress as getAddress5 } from "viem";
1662
+ var ZERO_ADDRESS4 = "0x0000000000000000000000000000000000000000";
1663
+ var ERC8004_TYPE = "https://eips.ethereum.org/EIPS/eip-8004#registration-v1";
1664
+ var B64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1665
+ function toBase64Utf8(str) {
1666
+ const bin = encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
1667
+ let out = "";
1668
+ for (let i = 0; i < bin.length; i += 3) {
1669
+ const a = bin.charCodeAt(i);
1670
+ const hasB = i + 1 < bin.length;
1671
+ const hasC = i + 2 < bin.length;
1672
+ const b = hasB ? bin.charCodeAt(i + 1) : 0;
1673
+ const c = hasC ? bin.charCodeAt(i + 2) : 0;
1674
+ out += B64_ALPHABET[a >> 2];
1675
+ out += B64_ALPHABET[(a & 3) << 4 | b >> 4];
1676
+ out += hasB ? B64_ALPHABET[(b & 15) << 2 | c >> 6] : "=";
1677
+ out += hasC ? B64_ALPHABET[c & 63] : "=";
1678
+ }
1679
+ return out;
1680
+ }
1681
+ function encodeIdentityMint(args, chainId) {
1682
+ const registry2 = getDeployment(chainId).identityRegistry;
1683
+ if (registry2 === ZERO_ADDRESS4) {
1684
+ throw unsupported(`ERC-8004 identity registry is not deployed on chain ${chainId} \u2014 mint on Base (8453).`);
1685
+ }
1686
+ if (!args.name)
1687
+ throw usage("`name` is required");
1688
+ if (!args.description)
1689
+ throw usage("`description` is required");
1690
+ const reg = {
1691
+ type: ERC8004_TYPE,
1692
+ name: args.name,
1693
+ description: args.description
1694
+ };
1695
+ if (args.image)
1696
+ reg.image = args.image;
1697
+ reg.services = [];
1698
+ reg.active = args.active ?? true;
1699
+ reg.x402Support = args.x402Support ?? false;
1700
+ const json = JSON.stringify(reg);
1701
+ const dataUri = `data:application/json;base64,${toBase64Utf8(json)}`;
1702
+ return {
1703
+ txs: [
1704
+ {
1705
+ to: getAddress5(registry2),
1706
+ data: encodeFunctionData5({
1707
+ abi: IDENTITY_REGISTRY_ABI,
1708
+ functionName: "register",
1709
+ args: [dataUri, []]
1710
+ }),
1711
+ value: "0x0",
1712
+ chainId
1713
+ }
1714
+ ],
1715
+ preconditions: [],
1716
+ description: `Mint an ERC-8004 agent identity "${args.name}" on chain ${chainId}.`,
1717
+ note: "ERC-8004 identity is minted on Base. Read the resulting token id from the mint receipt (chainId:tokenId); reference it when creating or joining syndicates."
1718
+ };
1719
+ }
1720
+
1721
+ // ../sdk/dist/encoders/eas.js
1722
+ import { encodeAbiParameters as encodeAbiParameters2, encodeFunctionData as encodeFunctionData6, getAddress as getAddress6, parseAbiParameters } from "viem";
1723
+ var ZERO_ADDRESS5 = "0x0000000000000000000000000000000000000000";
1724
+ var ZERO_BYTES322 = "0x0000000000000000000000000000000000000000000000000000000000000000";
1725
+ var JOIN_REQUEST_PARAMS = parseAbiParameters("uint256, uint256, address, string");
1726
+ var AGENT_APPROVED_PARAMS = parseAbiParameters("uint256, uint256, address");
1727
+ var toBig = (x) => typeof x === "bigint" ? x : BigInt(x);
1728
+ function formatMessageWithRef(message, referrerAgentId) {
1729
+ if (referrerAgentId == null)
1730
+ return message;
1731
+ return `[ref:${referrerAgentId}] ${message}`;
1732
+ }
1733
+ function formatMessageWithChain(message, sourceSlug, easSlug) {
1734
+ if (sourceSlug === easSlug)
1735
+ return message;
1736
+ return `[chain:${sourceSlug}] ${message}`;
1737
+ }
1738
+ function buildAttestTx(easAddress, schema, recipient, data, chainId) {
1739
+ return {
1740
+ to: getAddress6(easAddress),
1741
+ data: encodeFunctionData6({
1742
+ abi: EAS_ABI,
1743
+ functionName: "attest",
1744
+ args: [
1745
+ {
1746
+ schema,
1747
+ data: {
1748
+ recipient,
1749
+ expirationTime: 0n,
1750
+ revocable: true,
1751
+ refUID: ZERO_BYTES322,
1752
+ data,
1753
+ value: 0n
1754
+ }
1755
+ }
1756
+ ]
1757
+ }),
1758
+ value: "0x0",
1759
+ chainId
1760
+ };
1761
+ }
1762
+ function encodeJoinRequest(args, chainId) {
1763
+ const dep = getDeployment(chainId);
1764
+ const eas = dep.eas;
1765
+ if (eas.address === ZERO_ADDRESS5) {
1766
+ throw unsupported(`No EAS coordination path on chain ${chainId} \u2014 join requests require a chain whose attestations are pinned to Base.`);
1767
+ }
1768
+ const easSlug = getDeployment(eas.chainId).slug;
1769
+ const message = formatMessageWithChain(formatMessageWithRef(args.message, args.referrerAgentId), dep.slug, easSlug);
1770
+ const data = encodeAbiParameters2(JOIN_REQUEST_PARAMS, [
1771
+ toBig(args.syndicateId),
1772
+ toBig(args.agentId),
1773
+ getAddress6(args.vault),
1774
+ message
1775
+ ]);
1776
+ return {
1777
+ txs: [
1778
+ buildAttestTx(eas.address, eas.schemas.joinRequest, getAddress6(args.creator), data, eas.chainId)
1779
+ ],
1780
+ preconditions: [],
1781
+ description: `Request to join syndicate ${toBig(args.syndicateId).toString()} (vault ${getAddress6(args.vault)}).`,
1782
+ note: "EAS coordination attestation \u2014 always submitted on Base, even for syndicates on another chain. Revocable; the creator discovers it via the join-request schema."
1783
+ };
1784
+ }
1785
+ function encodeApproveAgent(args, chainId) {
1786
+ const dep = getDeployment(chainId);
1787
+ const eas = dep.eas;
1788
+ if (eas.address === ZERO_ADDRESS5) {
1789
+ throw unsupported(`No EAS coordination path on chain ${chainId} \u2014 approvals require a chain whose attestations are pinned to Base.`);
1790
+ }
1791
+ const registerTx = encodeRegisterAgent({
1792
+ vault: args.vault,
1793
+ agentAddress: args.agentAddress,
1794
+ agentId: toBig(args.agentId)
1795
+ }, chainId).txs[0];
1796
+ const data = encodeAbiParameters2(AGENT_APPROVED_PARAMS, [
1797
+ toBig(args.syndicateId),
1798
+ toBig(args.agentId),
1799
+ getAddress6(args.vault)
1800
+ ]);
1801
+ const attestTx = buildAttestTx(eas.address, eas.schemas.agentApproved, getAddress6(args.agentAddress), data, eas.chainId);
1802
+ return {
1803
+ txs: [registerTx, attestTx],
1804
+ preconditions: [],
1805
+ description: `Approve agent ${toBig(args.agentId).toString()} for syndicate ${toBig(args.syndicateId).toString()}: register on the vault (chain ${chainId}) + AGENT_APPROVED attestation on Base.`,
1806
+ note: "Vault owner only. Two txs on two chains: registerAgent runs on the syndicate's chain; the approval attestation runs on Base."
1807
+ };
1808
+ }
1809
+
1810
+ // ../sdk/dist/encoders/guardian.js
1811
+ import { encodeFunctionData as encodeFunctionData7, getAddress as getAddress7 } from "viem";
1812
+ var ZERO_VALUE5 = "0x0";
1813
+ function registryTx(chainId, data) {
1814
+ const registry2 = getDeployment(chainId).sherwood.guardianRegistry;
1815
+ if (registry2 === "0x0000000000000000000000000000000000000000") {
1816
+ throw unsupported(`Guardian registry is not deployed on chain ${chainId} yet`);
1817
+ }
1818
+ return { to: registry2, data, value: ZERO_VALUE5, chainId };
1819
+ }
1820
+ function parseUint(value, field) {
1821
+ if (typeof value === "bigint") {
1822
+ if (value < 0n)
1823
+ throw usage(`\`${field}\` must be a non-negative integer`);
1824
+ return value;
1825
+ }
1826
+ if (typeof value === "number") {
1827
+ if (!Number.isInteger(value) || value < 0) {
1828
+ throw usage(`\`${field}\` must be a non-negative integer`);
1829
+ }
1830
+ return BigInt(value);
1831
+ }
1832
+ if (!/^\d+$/.test(value)) {
1833
+ throw usage(`\`${field}\` must be a non-negative integer string, got "${value}"`);
1834
+ }
1835
+ return BigInt(value);
1836
+ }
1837
+ function encodeGuardianStake(args, chainId) {
1838
+ if (args.amount <= 0n)
1839
+ throw usage("`amount` must be > 0");
1840
+ const agentId = parseUint(args.agentId, "agentId");
1841
+ const data = encodeFunctionData7({
1842
+ abi: GUARDIAN_REGISTRY_ABI,
1843
+ functionName: "stakeAsGuardian",
1844
+ args: [args.amount, agentId]
1845
+ });
1846
+ const registry2 = getDeployment(chainId).sherwood.guardianRegistry;
1847
+ return {
1848
+ txs: [registryTx(chainId, data)],
1849
+ preconditions: [
1850
+ // Caller must have approved WOOD for the registry; agent should
1851
+ // check this client-side and prepend an ERC20.approve if needed.
1852
+ {
1853
+ type: "allowance",
1854
+ token: registry2,
1855
+ spender: registry2,
1856
+ min: args.amount.toString(),
1857
+ minDecimal: args.amount.toString()
1858
+ }
1859
+ ],
1860
+ description: `Stake ${args.amount.toString()} WOOD as guardian (agent ${agentId.toString()}).`,
1861
+ note: "Caller must have approved WOOD for the registry first. Stake locks the WOOD; use /prepare/guardian-unstake later to start the 7d unstake cooldown."
1862
+ };
1863
+ }
1864
+ var GUARDIAN_UNSTAKE_FN = {
1865
+ request: "requestUnstakeGuardian",
1866
+ cancel: "cancelUnstakeGuardian",
1867
+ claim: "claimUnstakeGuardian"
1868
+ };
1869
+ function encodeGuardianUnstake(args, chainId) {
1870
+ const fn = GUARDIAN_UNSTAKE_FN[args.action];
1871
+ if (!fn) {
1872
+ throw usage(`\`action\` must be one of: ${Object.keys(GUARDIAN_UNSTAKE_FN).join(", ")}`);
1873
+ }
1874
+ const data = encodeFunctionData7({
1875
+ abi: GUARDIAN_REGISTRY_ABI,
1876
+ functionName: fn,
1877
+ args: []
1878
+ });
1879
+ return {
1880
+ txs: [registryTx(chainId, data)],
1881
+ preconditions: [],
1882
+ description: `Guardian unstake ${args.action}.`,
1883
+ note: args.action === "request" ? "Starts a 7-day cooldown. After it elapses, call again with action=claim to withdraw." : args.action === "claim" ? "Withdraws the unstake amount once the 7-day cooldown has elapsed." : "Cancels a pending unstake request and re-activates the guardian."
1884
+ };
1885
+ }
1886
+ function encodeGuardianDelegate(args, chainId) {
1887
+ if (args.amount <= 0n)
1888
+ throw usage("`amount` must be > 0");
1889
+ const delegate = getAddress7(args.delegate);
1890
+ const data = encodeFunctionData7({
1891
+ abi: GUARDIAN_REGISTRY_ABI,
1892
+ functionName: "delegateStake",
1893
+ args: [delegate, args.amount]
1894
+ });
1895
+ return {
1896
+ txs: [registryTx(chainId, data)],
1897
+ preconditions: [],
1898
+ description: `Delegate ${args.amount.toString()} WOOD to ${delegate}.`,
1899
+ note: "Caller must have approved WOOD for the registry first. Delegation does NOT activate the delegate as a guardian \u2014 they must self-stake separately."
1900
+ };
1901
+ }
1902
+ var DELEGATION_UNSTAKE_FN = {
1903
+ request: "requestUnstakeDelegation",
1904
+ cancel: "cancelUnstakeDelegation",
1905
+ claim: "claimUnstakeDelegation"
1906
+ };
1907
+ function encodeDelegationUnstake(args, chainId) {
1908
+ const fn = DELEGATION_UNSTAKE_FN[args.action];
1909
+ if (!fn) {
1910
+ throw usage(`\`action\` must be one of: ${Object.keys(DELEGATION_UNSTAKE_FN).join(", ")}`);
1911
+ }
1912
+ const delegate = getAddress7(args.delegate);
1913
+ const data = encodeFunctionData7({
1914
+ abi: GUARDIAN_REGISTRY_ABI,
1915
+ functionName: fn,
1916
+ args: [delegate]
1917
+ });
1918
+ return {
1919
+ txs: [registryTx(chainId, data)],
1920
+ preconditions: [],
1921
+ description: `Delegation unstake ${args.action} for delegate ${delegate}.`,
1922
+ note: args.action === "request" ? "Starts a 7-day cooldown on the delegated amount." : args.action === "claim" ? "Withdraws the delegated amount once the 7-day cooldown has elapsed." : "Cancels a pending unstake of the delegation."
1923
+ };
1924
+ }
1925
+ function encodeSetCommission(args, chainId) {
1926
+ if (!Number.isInteger(args.bps) || args.bps < 0 || args.bps > 5e3) {
1927
+ throw usage("`bps` must be an integer in [0, 5000] (0\u201350%)");
1928
+ }
1929
+ const data = encodeFunctionData7({
1930
+ abi: GUARDIAN_REGISTRY_ABI,
1931
+ functionName: "setCommission",
1932
+ args: [args.bps]
1933
+ });
1934
+ return {
1935
+ txs: [registryTx(chainId, data)],
1936
+ preconditions: [],
1937
+ description: `Set DPoS commission to ${args.bps} bps.`,
1938
+ note: "Raise-rate is capped at 500 bps above the epoch-start rate; first set on a delegate is exempt. Decreases are unbounded."
1939
+ };
1940
+ }
1941
+ function encodeClaimProposalReward(args, chainId) {
1942
+ const proposalId = parseUint(args.proposalId, "proposalId");
1943
+ const data = encodeFunctionData7({
1944
+ abi: GUARDIAN_REGISTRY_ABI,
1945
+ functionName: "claimProposalReward",
1946
+ args: [proposalId]
1947
+ });
1948
+ return {
1949
+ txs: [registryTx(chainId, data)],
1950
+ preconditions: [],
1951
+ description: `Claim guardian-fee reward for proposal ${proposalId.toString()}.`,
1952
+ note: "Approver-side claim. Pulls the DPoS commission slice and seeds the delegator pool atomically."
1953
+ };
1954
+ }
1955
+ function encodeClaimDelegatorProposalReward(args, chainId) {
1956
+ const proposalId = parseUint(args.proposalId, "proposalId");
1957
+ const delegate = getAddress7(args.delegate);
1958
+ const data = encodeFunctionData7({
1959
+ abi: GUARDIAN_REGISTRY_ABI,
1960
+ functionName: "claimDelegatorProposalReward",
1961
+ args: [delegate, proposalId]
1962
+ });
1963
+ return {
1964
+ txs: [registryTx(chainId, data)],
1965
+ preconditions: [],
1966
+ description: `Claim delegator-share reward from delegate ${delegate} for proposal ${proposalId.toString()}.`,
1967
+ note: "Delegator-side claim. The approver must have claimed first to seed the delegator pool."
1968
+ };
1969
+ }
1970
+
1971
+ // ../sdk/dist/reads/syndicates.js
1972
+ import { getAddress as getAddress8 } from "viem";
1973
+
284
1974
  // src/strategies/moonwell-supply-template.ts
285
- import { encodeAbiParameters, encodeFunctionData } from "viem";
1975
+ import { encodeAbiParameters as encodeAbiParameters3, encodeFunctionData as encodeFunctionData8 } from "viem";
286
1976
  function buildInitData(underlying, mToken, supplyAmount, minRedeemAmount) {
287
- return encodeAbiParameters(
1977
+ return encodeAbiParameters3(
288
1978
  [
289
1979
  { type: "address" },
290
1980
  { type: "address" },
@@ -298,7 +1988,7 @@ function buildExecuteCalls(clone, underlying, supplyAmount) {
298
1988
  return [
299
1989
  {
300
1990
  target: underlying,
301
- data: encodeFunctionData({
1991
+ data: encodeFunctionData8({
302
1992
  abi: ERC20_ABI,
303
1993
  functionName: "approve",
304
1994
  args: [clone, supplyAmount]
@@ -307,7 +1997,7 @@ function buildExecuteCalls(clone, underlying, supplyAmount) {
307
1997
  },
308
1998
  {
309
1999
  target: clone,
310
- data: encodeFunctionData({
2000
+ data: encodeFunctionData8({
311
2001
  abi: BASE_STRATEGY_ABI,
312
2002
  functionName: "execute"
313
2003
  }),
@@ -319,7 +2009,7 @@ function buildSettleCalls(clone) {
319
2009
  return [
320
2010
  {
321
2011
  target: clone,
322
- data: encodeFunctionData({
2012
+ data: encodeFunctionData8({
323
2013
  abi: BASE_STRATEGY_ABI,
324
2014
  functionName: "settle"
325
2015
  }),
@@ -329,7 +2019,7 @@ function buildSettleCalls(clone) {
329
2019
  }
330
2020
 
331
2021
  // src/strategies/venice-inference-template.ts
332
- import { encodeAbiParameters as encodeAbiParameters2, encodeFunctionData as encodeFunctionData2 } from "viem";
2022
+ import { encodeAbiParameters as encodeAbiParameters4, encodeFunctionData as encodeFunctionData9 } from "viem";
333
2023
  var INIT_PARAMS_TYPES = [
334
2024
  {
335
2025
  type: "tuple",
@@ -349,13 +2039,13 @@ var INIT_PARAMS_TYPES = [
349
2039
  }
350
2040
  ];
351
2041
  function buildInitData2(params) {
352
- return encodeAbiParameters2(INIT_PARAMS_TYPES, [params]);
2042
+ return encodeAbiParameters4(INIT_PARAMS_TYPES, [params]);
353
2043
  }
354
2044
  function buildExecuteCalls2(clone, asset, assetAmount) {
355
2045
  return [
356
2046
  {
357
2047
  target: asset,
358
- data: encodeFunctionData2({
2048
+ data: encodeFunctionData9({
359
2049
  abi: ERC20_ABI,
360
2050
  functionName: "approve",
361
2051
  args: [clone, assetAmount]
@@ -364,7 +2054,7 @@ function buildExecuteCalls2(clone, asset, assetAmount) {
364
2054
  },
365
2055
  {
366
2056
  target: clone,
367
- data: encodeFunctionData2({
2057
+ data: encodeFunctionData9({
368
2058
  abi: BASE_STRATEGY_ABI,
369
2059
  functionName: "execute"
370
2060
  }),
@@ -376,7 +2066,7 @@ function buildSettleCalls2(clone) {
376
2066
  return [
377
2067
  {
378
2068
  target: clone,
379
- data: encodeFunctionData2({
2069
+ data: encodeFunctionData9({
380
2070
  abi: BASE_STRATEGY_ABI,
381
2071
  functionName: "settle"
382
2072
  }),
@@ -386,7 +2076,7 @@ function buildSettleCalls2(clone) {
386
2076
  }
387
2077
 
388
2078
  // src/strategies/aerodrome-lp-template.ts
389
- import { encodeAbiParameters as encodeAbiParameters3, encodeFunctionData as encodeFunctionData3 } from "viem";
2079
+ import { encodeAbiParameters as encodeAbiParameters5, encodeFunctionData as encodeFunctionData10 } from "viem";
390
2080
  var INIT_PARAMS_TYPES2 = [
391
2081
  {
392
2082
  type: "tuple",
@@ -408,13 +2098,13 @@ var INIT_PARAMS_TYPES2 = [
408
2098
  }
409
2099
  ];
410
2100
  function buildInitData3(params) {
411
- return encodeAbiParameters3(INIT_PARAMS_TYPES2, [params]);
2101
+ return encodeAbiParameters5(INIT_PARAMS_TYPES2, [params]);
412
2102
  }
413
2103
  function buildExecuteCalls3(clone, tokenA, amountA, tokenB, amountB) {
414
2104
  return [
415
2105
  {
416
2106
  target: tokenA,
417
- data: encodeFunctionData3({
2107
+ data: encodeFunctionData10({
418
2108
  abi: ERC20_ABI,
419
2109
  functionName: "approve",
420
2110
  args: [clone, amountA]
@@ -423,7 +2113,7 @@ function buildExecuteCalls3(clone, tokenA, amountA, tokenB, amountB) {
423
2113
  },
424
2114
  {
425
2115
  target: tokenB,
426
- data: encodeFunctionData3({
2116
+ data: encodeFunctionData10({
427
2117
  abi: ERC20_ABI,
428
2118
  functionName: "approve",
429
2119
  args: [clone, amountB]
@@ -432,7 +2122,7 @@ function buildExecuteCalls3(clone, tokenA, amountA, tokenB, amountB) {
432
2122
  },
433
2123
  {
434
2124
  target: clone,
435
- data: encodeFunctionData3({
2125
+ data: encodeFunctionData10({
436
2126
  abi: BASE_STRATEGY_ABI,
437
2127
  functionName: "execute"
438
2128
  }),
@@ -444,7 +2134,7 @@ function buildSettleCalls3(clone) {
444
2134
  return [
445
2135
  {
446
2136
  target: clone,
447
- data: encodeFunctionData3({
2137
+ data: encodeFunctionData10({
448
2138
  abi: BASE_STRATEGY_ABI,
449
2139
  functionName: "settle"
450
2140
  }),
@@ -454,7 +2144,7 @@ function buildSettleCalls3(clone) {
454
2144
  }
455
2145
 
456
2146
  // src/strategies/wsteth-moonwell-template.ts
457
- import { encodeAbiParameters as encodeAbiParameters4, encodeFunctionData as encodeFunctionData4, maxUint256 } from "viem";
2147
+ import { encodeAbiParameters as encodeAbiParameters6, encodeFunctionData as encodeFunctionData11, maxUint256 as maxUint2562 } from "viem";
458
2148
  var INIT_PARAMS_TYPES3 = [
459
2149
  {
460
2150
  type: "tuple",
@@ -473,14 +2163,14 @@ var INIT_PARAMS_TYPES3 = [
473
2163
  }
474
2164
  ];
475
2165
  function buildInitData4(params) {
476
- return encodeAbiParameters4(INIT_PARAMS_TYPES3, [params]);
2166
+ return encodeAbiParameters6(INIT_PARAMS_TYPES3, [params]);
477
2167
  }
478
2168
  function buildExecuteCalls4(clone, weth, supplyAmount) {
479
- const approveAmount = supplyAmount === 0n ? maxUint256 : supplyAmount;
2169
+ const approveAmount = supplyAmount === 0n ? maxUint2562 : supplyAmount;
480
2170
  return [
481
2171
  {
482
2172
  target: weth,
483
- data: encodeFunctionData4({
2173
+ data: encodeFunctionData11({
484
2174
  abi: ERC20_ABI,
485
2175
  functionName: "approve",
486
2176
  args: [clone, approveAmount]
@@ -489,7 +2179,7 @@ function buildExecuteCalls4(clone, weth, supplyAmount) {
489
2179
  },
490
2180
  {
491
2181
  target: clone,
492
- data: encodeFunctionData4({
2182
+ data: encodeFunctionData11({
493
2183
  abi: BASE_STRATEGY_ABI,
494
2184
  functionName: "execute"
495
2185
  }),
@@ -497,7 +2187,7 @@ function buildExecuteCalls4(clone, weth, supplyAmount) {
497
2187
  },
498
2188
  {
499
2189
  target: weth,
500
- data: encodeFunctionData4({
2190
+ data: encodeFunctionData11({
501
2191
  abi: ERC20_ABI,
502
2192
  functionName: "approve",
503
2193
  args: [clone, 0n]
@@ -510,7 +2200,7 @@ function buildSettleCalls4(clone) {
510
2200
  return [
511
2201
  {
512
2202
  target: clone,
513
- data: encodeFunctionData4({
2203
+ data: encodeFunctionData11({
514
2204
  abi: BASE_STRATEGY_ABI,
515
2205
  functionName: "settle"
516
2206
  }),
@@ -520,9 +2210,9 @@ function buildSettleCalls4(clone) {
520
2210
  }
521
2211
 
522
2212
  // src/strategies/mamo-yield-template.ts
523
- import { encodeAbiParameters as encodeAbiParameters5, encodeFunctionData as encodeFunctionData5 } from "viem";
2213
+ import { encodeAbiParameters as encodeAbiParameters7, encodeFunctionData as encodeFunctionData12 } from "viem";
524
2214
  function buildInitData5(underlying, mamoFactory, minRedeemAmount) {
525
- return encodeAbiParameters5(
2215
+ return encodeAbiParameters7(
526
2216
  [
527
2217
  { type: "address" },
528
2218
  { type: "address" },
@@ -535,7 +2225,7 @@ function buildExecuteCalls5(clone, underlying, amount) {
535
2225
  return [
536
2226
  {
537
2227
  target: underlying,
538
- data: encodeFunctionData5({
2228
+ data: encodeFunctionData12({
539
2229
  abi: ERC20_ABI,
540
2230
  functionName: "approve",
541
2231
  args: [clone, amount]
@@ -544,7 +2234,7 @@ function buildExecuteCalls5(clone, underlying, amount) {
544
2234
  },
545
2235
  {
546
2236
  target: clone,
547
- data: encodeFunctionData5({
2237
+ data: encodeFunctionData12({
548
2238
  abi: BASE_STRATEGY_ABI,
549
2239
  functionName: "execute"
550
2240
  }),
@@ -556,7 +2246,7 @@ function buildSettleCalls5(clone) {
556
2246
  return [
557
2247
  {
558
2248
  target: clone,
559
- data: encodeFunctionData5({
2249
+ data: encodeFunctionData12({
560
2250
  abi: BASE_STRATEGY_ABI,
561
2251
  functionName: "settle"
562
2252
  }),
@@ -566,13 +2256,13 @@ function buildSettleCalls5(clone) {
566
2256
  }
567
2257
 
568
2258
  // src/strategies/portfolio-template.ts
569
- import { encodeAbiParameters as encodeAbiParameters6, encodeFunctionData as encodeFunctionData6 } from "viem";
2259
+ import { encodeAbiParameters as encodeAbiParameters8, encodeFunctionData as encodeFunctionData13 } from "viem";
570
2260
  function buildInitData6(asset, swapAdapter, chainlinkVerifier, allocations, totalAmount, maxSlippageBps) {
571
2261
  const tokens = allocations.map((a) => a.token);
572
2262
  const weightsBps = allocations.map((a) => BigInt(a.weightBps));
573
2263
  const swapExtraData = allocations.map((a) => a.swapExtraData);
574
2264
  const priceDecimals = allocations.map((a) => a.priceDecimals);
575
- return encodeAbiParameters6(
2265
+ return encodeAbiParameters8(
576
2266
  [
577
2267
  { type: "address" },
578
2268
  { type: "address" },
@@ -601,7 +2291,7 @@ function buildExecuteCalls6(clone, asset, totalAmount) {
601
2291
  return [
602
2292
  {
603
2293
  target: asset,
604
- data: encodeFunctionData6({
2294
+ data: encodeFunctionData13({
605
2295
  abi: ERC20_ABI,
606
2296
  functionName: "approve",
607
2297
  args: [clone, totalAmount]
@@ -610,7 +2300,7 @@ function buildExecuteCalls6(clone, asset, totalAmount) {
610
2300
  },
611
2301
  {
612
2302
  target: clone,
613
- data: encodeFunctionData6({
2303
+ data: encodeFunctionData13({
614
2304
  abi: BASE_STRATEGY_ABI,
615
2305
  functionName: "execute"
616
2306
  }),
@@ -622,7 +2312,7 @@ function buildSettleCalls6(clone) {
622
2312
  return [
623
2313
  {
624
2314
  target: clone,
625
- data: encodeFunctionData6({
2315
+ data: encodeFunctionData13({
626
2316
  abi: BASE_STRATEGY_ABI,
627
2317
  functionName: "settle"
628
2318
  }),
@@ -632,9 +2322,9 @@ function buildSettleCalls6(clone) {
632
2322
  }
633
2323
 
634
2324
  // src/strategies/hyperliquid-perp-template.ts
635
- import { encodeAbiParameters as encodeAbiParameters7, encodeFunctionData as encodeFunctionData7, maxUint256 as maxUint2562 } from "viem";
2325
+ import { encodeAbiParameters as encodeAbiParameters9, encodeFunctionData as encodeFunctionData14, maxUint256 as maxUint2563 } from "viem";
636
2326
  function buildInitData7(asset, depositAmount, perpAssetIndex, leverage, maxPositionSize, maxTradesPerDay) {
637
- return encodeAbiParameters7(
2327
+ return encodeAbiParameters9(
638
2328
  [
639
2329
  { type: "address" },
640
2330
  { type: "uint256" },
@@ -647,11 +2337,11 @@ function buildInitData7(asset, depositAmount, perpAssetIndex, leverage, maxPosit
647
2337
  );
648
2338
  }
649
2339
  function buildExecuteCalls7(clone, asset, amount) {
650
- const approveAmount = amount === 0n ? maxUint2562 : amount;
2340
+ const approveAmount = amount === 0n ? maxUint2563 : amount;
651
2341
  return [
652
2342
  {
653
2343
  target: asset,
654
- data: encodeFunctionData7({
2344
+ data: encodeFunctionData14({
655
2345
  abi: ERC20_ABI,
656
2346
  functionName: "approve",
657
2347
  args: [clone, approveAmount]
@@ -660,7 +2350,7 @@ function buildExecuteCalls7(clone, asset, amount) {
660
2350
  },
661
2351
  {
662
2352
  target: clone,
663
- data: encodeFunctionData7({
2353
+ data: encodeFunctionData14({
664
2354
  abi: BASE_STRATEGY_ABI,
665
2355
  functionName: "execute"
666
2356
  }),
@@ -668,7 +2358,7 @@ function buildExecuteCalls7(clone, asset, amount) {
668
2358
  },
669
2359
  {
670
2360
  target: asset,
671
- data: encodeFunctionData7({
2361
+ data: encodeFunctionData14({
672
2362
  abi: ERC20_ABI,
673
2363
  functionName: "approve",
674
2364
  args: [clone, 0n]
@@ -681,7 +2371,7 @@ function buildSettleCalls7(clone) {
681
2371
  return [
682
2372
  {
683
2373
  target: clone,
684
- data: encodeFunctionData7({
2374
+ data: encodeFunctionData14({
685
2375
  abi: BASE_STRATEGY_ABI,
686
2376
  functionName: "settle"
687
2377
  }),
@@ -691,9 +2381,9 @@ function buildSettleCalls7(clone) {
691
2381
  }
692
2382
 
693
2383
  // src/strategies/hyperliquid-grid-template.ts
694
- import { encodeAbiParameters as encodeAbiParameters8, encodeFunctionData as encodeFunctionData8, maxUint256 as maxUint2563 } from "viem";
2384
+ import { encodeAbiParameters as encodeAbiParameters10, encodeFunctionData as encodeFunctionData15, maxUint256 as maxUint2564 } from "viem";
695
2385
  function buildInitData8(asset, depositAmount, leverage, maxOrderSize, maxOrdersPerTick, assetIndices) {
696
- return encodeAbiParameters8(
2386
+ return encodeAbiParameters10(
697
2387
  [
698
2388
  { type: "address" },
699
2389
  { type: "uint256" },
@@ -706,11 +2396,11 @@ function buildInitData8(asset, depositAmount, leverage, maxOrderSize, maxOrdersP
706
2396
  );
707
2397
  }
708
2398
  function buildExecuteCalls8(clone, asset, amount) {
709
- const approveAmount = amount === 0n ? maxUint2563 : amount;
2399
+ const approveAmount = amount === 0n ? maxUint2564 : amount;
710
2400
  return [
711
2401
  {
712
2402
  target: asset,
713
- data: encodeFunctionData8({
2403
+ data: encodeFunctionData15({
714
2404
  abi: ERC20_ABI,
715
2405
  functionName: "approve",
716
2406
  args: [clone, approveAmount]
@@ -719,7 +2409,7 @@ function buildExecuteCalls8(clone, asset, amount) {
719
2409
  },
720
2410
  {
721
2411
  target: clone,
722
- data: encodeFunctionData8({
2412
+ data: encodeFunctionData15({
723
2413
  abi: BASE_STRATEGY_ABI,
724
2414
  functionName: "execute"
725
2415
  }),
@@ -727,7 +2417,7 @@ function buildExecuteCalls8(clone, asset, amount) {
727
2417
  },
728
2418
  {
729
2419
  target: asset,
730
- data: encodeFunctionData8({
2420
+ data: encodeFunctionData15({
731
2421
  abi: ERC20_ABI,
732
2422
  functionName: "approve",
733
2423
  args: [clone, 0n]
@@ -740,7 +2430,7 @@ function buildSettleCalls8(clone) {
740
2430
  return [
741
2431
  {
742
2432
  target: clone,
743
- data: encodeFunctionData8({
2433
+ data: encodeFunctionData15({
744
2434
  abi: BASE_STRATEGY_ABI,
745
2435
  functionName: "settle"
746
2436
  }),
@@ -765,14 +2455,14 @@ function encodeV3Path(tokens, fees) {
765
2455
  }
766
2456
  function buildSwapExtraData(network, tokenIn, tokenOut, feeTier, hop) {
767
2457
  if (network === "robinhood-testnet") {
768
- return encodeAbiParameters9([{ type: "uint24" }], [feeTier]);
2458
+ return encodeAbiParameters11([{ type: "uint24" }], [feeTier]);
769
2459
  }
770
2460
  if (hop) {
771
2461
  const path = encodeV3Path([tokenIn, hop.via, tokenOut], [hop.feeIn, hop.feeOut]);
772
- const encoded2 = encodeAbiParameters9([{ type: "bytes" }], [path]);
2462
+ const encoded2 = encodeAbiParameters11([{ type: "bytes" }], [path]);
773
2463
  return `0x01${encoded2.slice(2)}`;
774
2464
  }
775
- const encoded = encodeAbiParameters9([{ type: "uint24" }], [feeTier]);
2465
+ const encoded = encodeAbiParameters11([{ type: "uint24" }], [feeTier]);
776
2466
  return `0x00${encoded.slice(2)}`;
777
2467
  }
778
2468
  async function detectSwapRoute(asset, token, preferredFeeTier) {
@@ -1174,7 +2864,7 @@ async function buildInitDataForTemplate(templateKey, opts, vault) {
1174
2864
  const feeTier = opts.feeTier || "3000";
1175
2865
  const tokenAddrs = opts.tokens.split(",").map((t) => {
1176
2866
  const trimmed = t.trim();
1177
- if (isAddress(trimmed)) return trimmed;
2867
+ if (isAddress2(trimmed)) return trimmed;
1178
2868
  const allTokens = tokens;
1179
2869
  const resolved = allTokens[trimmed.toUpperCase()];
1180
2870
  if (resolved && resolved !== ZERO) return resolved;
@@ -1319,7 +3009,7 @@ function resolveSwapAdapter() {
1319
3009
  process.exit(1);
1320
3010
  }
1321
3011
  function resolveToken(symbolOrAddress) {
1322
- if (isAddress(symbolOrAddress)) return symbolOrAddress;
3012
+ if (isAddress2(symbolOrAddress)) return symbolOrAddress;
1323
3013
  const upper = symbolOrAddress.toUpperCase();
1324
3014
  const tokens = TOKENS();
1325
3015
  const tokenMap = {
@@ -1383,11 +3073,11 @@ function registerStrategyTemplateCommands(strategy2) {
1383
3073
  console.log();
1384
3074
  console.log(chalk.yellow(" No strategy templates deployed on this network."));
1385
3075
  }
1386
- const unavailable = TEMPLATES.filter((t) => templates[t.addressKey] === ZERO);
1387
- if (unavailable.length > 0) {
3076
+ const unavailable2 = TEMPLATES.filter((t) => templates[t.addressKey] === ZERO);
3077
+ if (unavailable2.length > 0) {
1388
3078
  console.log();
1389
3079
  console.log(chalk.dim(` Not available on ${network}:`));
1390
- for (const t of unavailable) {
3080
+ for (const t of unavailable2) {
1391
3081
  console.log(chalk.dim(` ${t.name} (${t.key})`));
1392
3082
  }
1393
3083
  }
@@ -1398,7 +3088,7 @@ function registerStrategyTemplateCommands(strategy2) {
1398
3088
  });
1399
3089
  strategy2.command("clone").description("Clone a strategy template and initialize it").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio, hyperliquid-perp, hyperliquid-grid").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1400
3090
  const vault = opts.vault;
1401
- if (!isAddress(vault)) {
3091
+ if (!isAddress2(vault)) {
1402
3092
  console.error(chalk.red("Invalid vault address"));
1403
3093
  process.exit(1);
1404
3094
  }
@@ -1467,11 +3157,11 @@ function registerStrategyTemplateCommands(strategy2) {
1467
3157
  strategy2.command("init").description("Initialize an already-deployed but uninitialized strategy clone").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio, hyperliquid-perp, hyperliquid-grid").requiredOption("--clone <address>", "Clone address to initialize").requiredOption("--vault <address>", "Vault address").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1468
3158
  const clone = opts.clone;
1469
3159
  const vault = opts.vault;
1470
- if (!isAddress(clone)) {
3160
+ if (!isAddress2(clone)) {
1471
3161
  console.error(chalk.red("Invalid clone address"));
1472
3162
  process.exit(1);
1473
3163
  }
1474
- if (!isAddress(vault)) {
3164
+ if (!isAddress2(vault)) {
1475
3165
  console.error(chalk.red("Invalid vault address"));
1476
3166
  process.exit(1);
1477
3167
  }
@@ -1520,13 +3210,141 @@ function registerStrategyTemplateCommands(strategy2) {
1520
3210
  console.log(` Proposer: ${chalk.green(getAccount().address)}`);
1521
3211
  console.log();
1522
3212
  });
1523
- strategy2.command("propose").description("Clone + init + build calls + submit governance proposal (all-in-one)").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio, hyperliquid-perp, hyperliquid-grid").requiredOption("--vault <address>", "Vault address").option("--write-calls <dir>", "Write execute/settle JSON to directory (skip proposal submission)").option("--name <name>", "Proposal name").option("--description <text>", "Proposal description").option("--performance-fee <bps>", "Agent fee in bps").option("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
3213
+ strategy2.command("propose").description("Clone + init + build calls + submit governance proposal (all-in-one)").argument("<template>", "Template: moonwell-supply, aerodrome-lp, venice-inference, wsteth-moonwell, mamo-yield, portfolio, hyperliquid-perp, hyperliquid-grid").requiredOption("--vault <address>", "Vault address").option("--write-calls <dir>", "Write execute/settle JSON to directory (skip proposal submission)").option("--name <name>", "Proposal name").option("--description <text>", "Proposal description").option("--performance-fee <bps>", "Agent fee in bps").option("--duration <duration>", "Strategy duration (7d, 24h, etc.)").option("--proposer <address>", "Proposer/agent address (required for --calldata-only \u2014 the external signer)").option("--metadata-uri <uri>", "Metadata URI (required for --calldata-only \u2014 IPFS pin needs a signer)").option("--salt <bytes32>", "CREATE2 salt for the deterministic clone (--calldata-only; default: derived from inputs)").option("--amount <n>", "Asset amount to deploy").option("--min-redeem <n>", "Min asset on settlement (Moonwell)").option("--token <symbol>", "Asset token symbol (default: USDC)").option("--asset <symbol>", "Asset token (USDC, VVV, or address)").option("--agent <address>", "Agent wallet (Venice, default: your wallet)").option("--min-vvv <n>", "Min VVV from swap (Venice)").option("--single-hop", "Single-hop Aerodrome swap (Venice)").option("--token-a <address>", "Token A (Aerodrome)").option("--token-b <address>", "Token B (Aerodrome)").option("--amount-a <n>", "Token A amount (Aerodrome)").option("--amount-b <n>", "Token B amount (Aerodrome)").option("--stable", "Stable pool (Aerodrome)").option("--gauge <address>", "Gauge address (Aerodrome)").option("--lp-token <address>", "LP token address (Aerodrome)").option("--min-a-out <n>", "Min token A on settle (Aerodrome)").option("--min-b-out <n>", "Min token B on settle (Aerodrome)").option("--slippage <bps>", "Slippage tolerance in bps (wstETH, default: 500 = 5%)").option("--mamo-factory <address>", "Mamo StrategyFactory address (Mamo)").option("--tokens <list>", "Comma-separated token addresses or symbols (Portfolio)").option("--weights <list>", "Comma-separated weights in bps, must sum to 10000 (Portfolio)").option("--max-slippage <bps>", "Max slippage bps (Portfolio, default: 500)").option("--fee-tier <n>", "Pool fee tier (Portfolio, default: 3000)").option("--swap-adapter <address>", "Swap adapter address (Portfolio)").option("--leverage <number>", "Leverage multiplier (Hyperliquid Perp, default: 10)").option("--asset-index <number>", "Perp asset index (Hyperliquid Perp, default: 0 for BTC)").option("--max-position <amount>", "Max position size in USD (Hyperliquid Perp, default: 100000)").option("--max-trades-per-day <n>", "Max trades per day (Hyperliquid Perp, default: 50)").option("--max-order-size <amount>", "Max single-order size in asset units (Hyperliquid Grid, default: 10000)").option("--max-orders-per-tick <n>", "Max orders per tick (Hyperliquid Grid, default: 20)").option("--asset-indices <list>", "Comma-separated perp asset indices (Hyperliquid Grid, default: 0)").action(async (templateKey, opts) => {
1524
3214
  const vault = opts.vault;
1525
- if (!isAddress(vault)) {
3215
+ if (!isAddress2(vault)) {
1526
3216
  console.error(chalk.red("Invalid vault address"));
1527
3217
  process.exit(1);
1528
3218
  }
1529
3219
  const { def, address: templateAddr } = resolveTemplate(templateKey);
3220
+ if (isCalldataOnly()) {
3221
+ const missing = [];
3222
+ if (!opts.proposer) missing.push("--proposer <address> (the external signer)");
3223
+ if (!opts.metadataUri) missing.push("--metadata-uri <uri> (IPFS pin needs a signer)");
3224
+ if (!opts.performanceFee) missing.push("--performance-fee <bps>");
3225
+ if (!opts.duration) missing.push("--duration <d>");
3226
+ if (missing.length > 0) {
3227
+ console.error(chalk.red(`--calldata-only requires: ${missing.join(", ")}`));
3228
+ process.exit(1);
3229
+ }
3230
+ const proposer = opts.proposer;
3231
+ if (!isAddress2(proposer)) {
3232
+ console.error(chalk.red("Invalid --proposer address"));
3233
+ process.exit(1);
3234
+ }
3235
+ const performanceFeeBps2 = Number(opts.performanceFee);
3236
+ if (!Number.isInteger(performanceFeeBps2) || performanceFeeBps2 < 0 || performanceFeeBps2 > 1e4) {
3237
+ console.error(chalk.red("--performance-fee must be an integer 0-10000 (bps)"));
3238
+ process.exit(1);
3239
+ }
3240
+ try {
3241
+ const publicClient = getPublicClient();
3242
+ const [isRegistered, paused] = await Promise.all([
3243
+ publicClient.readContract({
3244
+ address: vault,
3245
+ abi: SYNDICATE_VAULT_ABI,
3246
+ functionName: "isAgent",
3247
+ args: [proposer]
3248
+ }),
3249
+ publicClient.readContract({
3250
+ address: vault,
3251
+ abi: SYNDICATE_VAULT_ABI,
3252
+ functionName: "paused"
3253
+ })
3254
+ ]);
3255
+ if (!isRegistered) {
3256
+ console.error(
3257
+ chalk.red(
3258
+ `--proposer ${proposer} is not a registered agent on ${vault}; cloneAndInitDeterministic and propose will both revert.`
3259
+ )
3260
+ );
3261
+ console.error(chalk.yellow(` Register first: sherwood syndicate approve --wallet ${proposer}`));
3262
+ process.exit(1);
3263
+ }
3264
+ if (paused) {
3265
+ console.error(chalk.red(`Vault ${vault} is paused \u2014 the proposal would revert.`));
3266
+ process.exit(1);
3267
+ }
3268
+ for (const check of computeProposalAssetChecks(templateKey, opts)) {
3269
+ if (check.amount === 0n) continue;
3270
+ const [balance, decimals] = await Promise.all([
3271
+ publicClient.readContract({
3272
+ address: check.asset,
3273
+ abi: erc20Abi,
3274
+ functionName: "balanceOf",
3275
+ args: [vault]
3276
+ }),
3277
+ publicClient.readContract({
3278
+ address: check.asset,
3279
+ abi: erc20Abi,
3280
+ functionName: "decimals"
3281
+ })
3282
+ ]);
3283
+ if (balance < check.amount) {
3284
+ console.error(
3285
+ chalk.red(
3286
+ `Vault ${vault} holds ${formatUnits2(balance, decimals)} ${check.label} but the proposal needs ${formatUnits2(check.amount, decimals)} ${check.label}.`
3287
+ )
3288
+ );
3289
+ console.error(chalk.yellow(` Hint: --amount is in human units (e.g. "10" = 10 ${check.label}), not raw decimals.`));
3290
+ process.exit(1);
3291
+ }
3292
+ }
3293
+ } catch (err) {
3294
+ console.error(chalk.red(`Preflight read failed: ${err instanceof Error ? err.message : String(err)}`));
3295
+ process.exit(1);
3296
+ }
3297
+ if (!opts.agent) opts.agent = proposer;
3298
+ const { parseDuration: parseDuration3 } = await import("./governor-UBUNIO3U.js");
3299
+ const strategyDuration2 = parseDuration3(opts.duration);
3300
+ const chainId = getChain().id;
3301
+ try {
3302
+ const built = await buildInitDataForTemplate(templateKey, opts, vault);
3303
+ const deploy = encodeStrategyDeploy(
3304
+ {
3305
+ template: templateAddr,
3306
+ vault,
3307
+ proposer,
3308
+ initData: built.initData,
3309
+ salt: opts.salt
3310
+ },
3311
+ chainId
3312
+ );
3313
+ const { executeCalls: executeCalls2, settleCalls: settleCalls2 } = buildCallsForTemplate(
3314
+ templateKey,
3315
+ deploy.clone,
3316
+ built.asset,
3317
+ built.assetAmount,
3318
+ built.extraApprovals
3319
+ );
3320
+ const proposeAction = encodePropose(
3321
+ {
3322
+ vault,
3323
+ strategy: deploy.clone,
3324
+ metadataURI: opts.metadataUri,
3325
+ performanceFeeBps: performanceFeeBps2,
3326
+ strategyDuration: strategyDuration2,
3327
+ executeCalls: executeCalls2,
3328
+ settlementCalls: settleCalls2
3329
+ },
3330
+ chainId
3331
+ );
3332
+ emitCalldata({
3333
+ txs: [...deploy.txs, ...proposeAction.txs],
3334
+ preconditions: [...deploy.preconditions, ...proposeAction.preconditions],
3335
+ // Surface the deterministic clone + salt as structured fields (also
3336
+ // baked into tx 2's `strategy` arg + the execute/settle calls).
3337
+ clone: deploy.clone,
3338
+ salt: deploy.salt,
3339
+ description: `Keyless ${def.name} proposal on ${vault}: (1) deploy clone at ${deploy.clone} (deterministic CREATE2, salt ${deploy.salt}); (2) governor.propose referencing it.`,
3340
+ note: "Broadcast SEQUENTIALLY: send tx 1 (cloneAndInitDeterministic) and WAIT for it to confirm, then send tx 2 (propose). If tx 1 reverts \u2014 proposer not a registered agent, proposer != tx-1 signer, template not allowlisted, or vault unregistered \u2014 do NOT send tx 2: it would create a proposal referencing an undeployed strategy. Send tx 1 from the --proposer wallet (the factory requires proposer == msg.sender, and msg.sender must be a vault owner/agent). The clone address is pinned by (factory, template, vault, salt) and is already baked into tx 2 + the execute/settle calls. The factory binds the salt to the vault and only the vault's owner/agents may deploy there, so the predicted address can't be front-run."
3341
+ });
3342
+ } catch (err) {
3343
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
3344
+ process.exit(1);
3345
+ }
3346
+ return;
3347
+ }
1530
3348
  const account = getAccount();
1531
3349
  const preflightSpinner = ora("Preflight checks...").start();
1532
3350
  try {
@@ -1577,7 +3395,7 @@ function registerStrategyTemplateCommands(strategy2) {
1577
3395
  preflightSpinner.fail("Preflight failed");
1578
3396
  console.error(
1579
3397
  chalk.red(
1580
- ` Vault ${vault} holds ${formatUnits(balance, decimals)} ${check.label} but proposal requires ${formatUnits(check.amount, decimals)} ${check.label}.`
3398
+ ` Vault ${vault} holds ${formatUnits2(balance, decimals)} ${check.label} but proposal requires ${formatUnits2(check.amount, decimals)} ${check.label}.`
1581
3399
  )
1582
3400
  );
1583
3401
  console.error(
@@ -1763,7 +3581,7 @@ function registerStrategyTemplateCommands(strategy2) {
1763
3581
  console.log();
1764
3582
  });
1765
3583
  strategy2.command("status").description("Show portfolio strategy status \u2014 allocations, balances, drift, PnL (Portfolio only)").argument("<clone>", "Strategy clone address").option("--json", "Output machine-readable JSON for agent consumption").action(async (cloneArg, opts) => {
1766
- if (!isAddress(cloneArg)) {
3584
+ if (!isAddress2(cloneArg)) {
1767
3585
  console.error(chalk.red("Invalid clone address"));
1768
3586
  process.exit(1);
1769
3587
  }
@@ -1826,13 +3644,13 @@ function registerStrategyTemplateCommands(strategy2) {
1826
3644
  } catch {
1827
3645
  }
1828
3646
  const pricesAvailable = prices.some((p) => p !== null);
1829
- const assetBalanceNum = Number(formatUnits(assetBalance, assetDecimals));
3647
+ const assetBalanceNum = Number(formatUnits2(assetBalance, assetDecimals));
1830
3648
  let totalPortfolioValue = assetBalanceNum;
1831
3649
  let totalInvested = 0n;
1832
3650
  const allocStatuses = allocations.map((alloc, i) => {
1833
3651
  const [balance, symbol, decimals] = tokenData[i];
1834
- const balanceNum = Number(formatUnits(balance, decimals));
1835
- const investedNum = Number(formatUnits(alloc.investedAmount, assetDecimals));
3652
+ const balanceNum = Number(formatUnits2(balance, decimals));
3653
+ const investedNum = Number(formatUnits2(alloc.investedAmount, assetDecimals));
1836
3654
  totalInvested += alloc.investedAmount;
1837
3655
  const price = prices[i];
1838
3656
  let currentValue = null;
@@ -1845,9 +3663,9 @@ function registerStrategyTemplateCommands(strategy2) {
1845
3663
  symbol,
1846
3664
  decimals,
1847
3665
  targetWeightBps: Number(alloc.targetWeightBps),
1848
- balance: formatUnits(balance, decimals),
3666
+ balance: formatUnits2(balance, decimals),
1849
3667
  balanceRaw: balance.toString(),
1850
- investedAmount: formatUnits(alloc.investedAmount, assetDecimals),
3668
+ investedAmount: formatUnits2(alloc.investedAmount, assetDecimals),
1851
3669
  currentPrice: price?.price ?? null,
1852
3670
  currentValue,
1853
3671
  actualWeightBps: null,
@@ -1871,7 +3689,7 @@ function registerStrategyTemplateCommands(strategy2) {
1871
3689
  }
1872
3690
  }
1873
3691
  }
1874
- const totalInvestedNum = Number(formatUnits(totalInvested, assetDecimals));
3692
+ const totalInvestedNum = Number(formatUnits2(totalInvested, assetDecimals));
1875
3693
  const totalPnl = pricesAvailable ? totalPortfolioValue - totalInvestedNum : null;
1876
3694
  const totalPnlPct = totalPnl !== null && totalInvestedNum > 0 ? totalPnl / totalInvestedNum * 100 : null;
1877
3695
  spinner?.succeed("Strategy state loaded");
@@ -1883,9 +3701,9 @@ function registerStrategyTemplateCommands(strategy2) {
1883
3701
  state: stateStr,
1884
3702
  network,
1885
3703
  asset: { address: assetAddr, symbol: assetSymbol, decimals: assetDecimals },
1886
- totalDeployed: formatUnits(totalAmount, assetDecimals),
3704
+ totalDeployed: formatUnits2(totalAmount, assetDecimals),
1887
3705
  maxSlippageBps: Number(maxSlippage),
1888
- assetBalance: formatUnits(assetBalance, assetDecimals),
3706
+ assetBalance: formatUnits2(assetBalance, assetDecimals),
1889
3707
  pricesAvailable,
1890
3708
  priceSource,
1891
3709
  portfolio: {
@@ -1910,9 +3728,9 @@ function registerStrategyTemplateCommands(strategy2) {
1910
3728
  console.log(` Proposer: ${chalk.cyan(proposer)}`);
1911
3729
  console.log(` State: ${stateColor(stateStr)}`);
1912
3730
  console.log(` Asset: ${assetSymbol} (${assetAddr})`);
1913
- console.log(` Deployed: ${formatUnits(totalAmount, assetDecimals)} ${assetSymbol}`);
3731
+ console.log(` Deployed: ${formatUnits2(totalAmount, assetDecimals)} ${assetSymbol}`);
1914
3732
  console.log(` Max Slip: ${Number(maxSlippage) / 100}%`);
1915
- console.log(` Asset held: ${formatUnits(assetBalance, assetDecimals)} ${assetSymbol}`);
3733
+ console.log(` Asset held: ${formatUnits2(assetBalance, assetDecimals)} ${assetSymbol}`);
1916
3734
  if (pricesAvailable) {
1917
3735
  const pvStr = totalPortfolioValue.toFixed(6);
1918
3736
  const pnlStr = totalPnl !== null ? (totalPnl >= 0 ? "+" : "") + totalPnl.toFixed(6) : "n/a";
@@ -1954,7 +3772,7 @@ function registerStrategyTemplateCommands(strategy2) {
1954
3772
  }
1955
3773
  }
1956
3774
  console.log(chalk.dim("\u2500".repeat(70)));
1957
- console.log(` Total invested: ${formatUnits(totalInvested, assetDecimals)} ${assetSymbol}`);
3775
+ console.log(` Total invested: ${formatUnits2(totalInvested, assetDecimals)} ${assetSymbol}`);
1958
3776
  if (pricesAvailable && maxDriftToken) {
1959
3777
  const absMaxDrift = Math.abs(maxDriftBps);
1960
3778
  const driftColor = absMaxDrift > 500 ? chalk.red : absMaxDrift > 200 ? chalk.yellow : chalk.green;
@@ -1973,7 +3791,7 @@ function registerStrategyTemplateCommands(strategy2) {
1973
3791
  }
1974
3792
  });
1975
3793
  strategy2.command("rebalance").description("Rebalance a portfolio strategy \u2014 sell all positions, re-buy at target weights (Portfolio only)").argument("<clone>", "Strategy clone address").option("--new-weights <list>", "Comma-separated new weights in bps (must sum to 10000). Updates weights before rebalancing.").option("--max-slippage <bps>", "New max slippage bps (used with --new-weights)").option("--dry-run", "Show what would happen without executing").action(async (cloneArg, opts) => {
1976
- if (!isAddress(cloneArg)) {
3794
+ if (!isAddress2(cloneArg)) {
1977
3795
  console.error(chalk.red("Invalid clone address"));
1978
3796
  process.exit(1);
1979
3797
  }
@@ -2017,7 +3835,7 @@ function registerStrategyTemplateCommands(strategy2) {
2017
3835
  const [balance, symbol, decimals] = tokenData[i];
2018
3836
  const weightPct = (Number(alloc.targetWeightBps) / 100).toFixed(1) + "%";
2019
3837
  console.log(
2020
- ` ${chalk.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} bal: ${formatUnits(balance, decimals)}`
3838
+ ` ${chalk.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} bal: ${formatUnits2(balance, decimals)}`
2021
3839
  );
2022
3840
  }
2023
3841
  let newWeightsBps;
@@ -2061,7 +3879,7 @@ function registerStrategyTemplateCommands(strategy2) {
2061
3879
  });
2062
3880
  const updateSpinner = ora("Updating target weights...").start();
2063
3881
  try {
2064
- const innerData = encodeAbiParameters9(
3882
+ const innerData = encodeAbiParameters11(
2065
3883
  [{ type: "uint256[]" }, { type: "uint256" }, { type: "bytes[]" }],
2066
3884
  [newWeightsBps.map((w) => BigInt(w)), BigInt(maxSlip), swapData]
2067
3885
  );
@@ -2121,9 +3939,9 @@ function registerStrategyTemplateCommands(strategy2) {
2121
3939
  const alloc = postAllocations[i];
2122
3940
  const [balance, symbol, decimals] = postData[i];
2123
3941
  const weightPct = (Number(alloc.targetWeightBps) / 100).toFixed(1) + "%";
2124
- const investedStr = formatUnits(alloc.investedAmount, assetDecimals);
3942
+ const investedStr = formatUnits2(alloc.investedAmount, assetDecimals);
2125
3943
  console.log(
2126
- ` ${chalk.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} invested: ${investedStr.padEnd(14)} bal: ${formatUnits(balance, decimals)}`
3944
+ ` ${chalk.bold(symbol.padEnd(8))} ${weightPct.padEnd(8)} invested: ${investedStr.padEnd(14)} bal: ${formatUnits2(balance, decimals)}`
2127
3945
  );
2128
3946
  }
2129
3947
  console.log();
@@ -2205,7 +4023,7 @@ async function getActiveSyndicates2(creator) {
2205
4023
  }
2206
4024
 
2207
4025
  // src/commands/venice.ts
2208
- import { formatUnits as formatUnits2, isAddress as isAddress2, parseUnits as parseUnits2 } from "viem";
4026
+ import { formatUnits as formatUnits3, isAddress as isAddress3, parseUnits as parseUnits2 } from "viem";
2209
4027
  import chalk2 from "chalk";
2210
4028
  import ora2 from "ora";
2211
4029
  import { readFileSync } from "fs";
@@ -2229,7 +4047,7 @@ function registerVeniceCommands(program2) {
2229
4047
  console.log(chalk2.yellow(" Use the VeniceInferenceStrategy via a proposal to distribute sVVV to agents."));
2230
4048
  process.exit(1);
2231
4049
  }
2232
- checkSpinner.succeed(`sVVV balance: ${formatUnits2(sVvvBalance, 18)}`);
4050
+ checkSpinner.succeed(`sVVV balance: ${formatUnits3(sVvvBalance, 18)}`);
2233
4051
  } catch (err) {
2234
4052
  checkSpinner.fail("Failed to check sVVV balance");
2235
4053
  console.error(chalk2.red(formatContractError(err)));
@@ -2270,10 +4088,10 @@ function registerVeniceCommands(program2) {
2270
4088
  args: [account.address]
2271
4089
  });
2272
4090
  if (vvvBalance < amount) {
2273
- balSpinner.fail(`Insufficient VVV: have ${formatUnits2(vvvBalance, 18)}, need ${opts.amount}`);
4091
+ balSpinner.fail(`Insufficient VVV: have ${formatUnits3(vvvBalance, 18)}, need ${opts.amount}`);
2274
4092
  process.exit(1);
2275
4093
  }
2276
- balSpinner.succeed(`VVV balance: ${formatUnits2(vvvBalance, 18)}`);
4094
+ balSpinner.succeed(`VVV balance: ${formatUnits3(vvvBalance, 18)}`);
2277
4095
  } catch (err) {
2278
4096
  balSpinner.fail("Failed to check VVV balance");
2279
4097
  console.error(chalk2.red(formatContractError(err)));
@@ -2313,7 +4131,7 @@ function registerVeniceCommands(program2) {
2313
4131
  functionName: "balanceOf",
2314
4132
  args: [account.address]
2315
4133
  });
2316
- stakeSpinner.succeed(`Staked ${opts.amount} VVV \u2192 ${formatUnits2(sVvvBalance, 18)} sVVV`);
4134
+ stakeSpinner.succeed(`Staked ${opts.amount} VVV \u2192 ${formatUnits3(sVvvBalance, 18)} sVVV`);
2317
4135
  console.log(chalk2.dim(" You can now run: sherwood venice provision"));
2318
4136
  } catch (err) {
2319
4137
  stakeSpinner.fail("Stake failed");
@@ -2348,12 +4166,12 @@ function registerVeniceCommands(program2) {
2348
4166
  }
2349
4167
  if (sVvvBalance < amount) {
2350
4168
  balSpinner.fail(
2351
- `Insufficient sVVV: have ${formatUnits2(sVvvBalance, 18)}, need ${opts.amount}`
4169
+ `Insufficient sVVV: have ${formatUnits3(sVvvBalance, 18)}, need ${opts.amount}`
2352
4170
  );
2353
4171
  console.log(chalk2.yellow(" Stake VVV first (see VeniceInferenceStrategy proposal flow)."));
2354
4172
  process.exit(1);
2355
4173
  }
2356
- balSpinner.succeed(`sVVV balance: ${formatUnits2(sVvvBalance, 18)}`);
4174
+ balSpinner.succeed(`sVVV balance: ${formatUnits3(sVvvBalance, 18)}`);
2357
4175
  const quoteSpinner = ora2("Quoting DIEM output...").start();
2358
4176
  let expected;
2359
4177
  try {
@@ -2374,7 +4192,7 @@ function registerVeniceCommands(program2) {
2374
4192
  }
2375
4193
  const minOut = expected * (10000n - slippageBps) / 10000n;
2376
4194
  quoteSpinner.succeed(
2377
- `Expected: ${formatUnits2(expected, 18)} DIEM (minOut @ ${slippageBps}bps: ${formatUnits2(minOut, 18)})`
4195
+ `Expected: ${formatUnits3(expected, 18)} DIEM (minOut @ ${slippageBps}bps: ${formatUnits3(minOut, 18)})`
2378
4196
  );
2379
4197
  const MIN_SPENDABLE_DIEM = parseUnits2("0.1", 18);
2380
4198
  let currentDiem = 0n;
@@ -2394,8 +4212,8 @@ function registerVeniceCommands(program2) {
2394
4212
  console.log();
2395
4213
  console.log(chalk2.red(" \u26A0 Venice requires \u2265 0.1 DIEM minimum before ANY DIEM credit"));
2396
4214
  console.log(chalk2.red(" becomes spendable on inference calls."));
2397
- console.log(chalk2.red(` Projected DIEM after mint: ${formatUnits2(projectedDiem, 18)} (short by ${formatUnits2(shortfall, 18)})`));
2398
- console.log(chalk2.red(` Approx additional sVVV needed: ${formatUnits2(sVvvNeeded, 18)}`));
4215
+ console.log(chalk2.red(` Projected DIEM after mint: ${formatUnits3(projectedDiem, 18)} (short by ${formatUnits3(shortfall, 18)})`));
4216
+ console.log(chalk2.red(` Approx additional sVVV needed: ${formatUnits3(sVvvNeeded, 18)}`));
2399
4217
  console.log(chalk2.dim(" Below threshold, this DIEM sits idle \u2014 consider acquiring"));
2400
4218
  console.log(chalk2.dim(" more sVVV first, or use x402 USDC pay-per-request."));
2401
4219
  }
@@ -2439,7 +4257,7 @@ function registerVeniceCommands(program2) {
2439
4257
  });
2440
4258
  const delta = diemAfter - diemBefore;
2441
4259
  mintSpinner.succeed(
2442
- `Minted ${formatUnits2(delta > 0n ? delta : diemAfter, 18)} DIEM` + (delta > 0n ? ` (balance: ${formatUnits2(diemAfter, 18)})` : "")
4260
+ `Minted ${formatUnits3(delta > 0n ? delta : diemAfter, 18)} DIEM` + (delta > 0n ? ` (balance: ${formatUnits3(diemAfter, 18)})` : "")
2443
4261
  );
2444
4262
  console.log(chalk2.dim(` Tx: ${hash}`));
2445
4263
  console.log(chalk2.yellow(" Note: Venice requires \u2265 0.1 DIEM to spend ANY DIEM credit, and"));
@@ -2454,7 +4272,7 @@ function registerVeniceCommands(program2) {
2454
4272
  });
2455
4273
  venice.command("status").description("Show Venice inference status: sVVV balances, DIEM, API key").requiredOption("--vault <address>", "Vault address").action(async (opts) => {
2456
4274
  const vaultAddress = opts.vault;
2457
- if (!isAddress2(vaultAddress)) {
4275
+ if (!isAddress3(vaultAddress)) {
2458
4276
  console.error(chalk2.red(`Invalid vault address: ${opts.vault}`));
2459
4277
  process.exit(1);
2460
4278
  }
@@ -2517,18 +4335,18 @@ function registerVeniceCommands(program2) {
2517
4335
  console.log(chalk2.bold("Venice Inference Status"));
2518
4336
  console.log(chalk2.dim("\u2500".repeat(50)));
2519
4337
  console.log(chalk2.bold("\n Vault"));
2520
- console.log(` Profit available: ${formatUnits2(profit, assetDecimals)} ${assetSymbol}`);
2521
- console.log(` VVV (unstaked): ${formatUnits2(vaultVvvBalance, 18)}`);
4338
+ console.log(` Profit available: ${formatUnits3(profit, assetDecimals)} ${assetSymbol}`);
4339
+ console.log(` VVV (unstaked): ${formatUnits3(vaultVvvBalance, 18)}`);
2522
4340
  console.log(chalk2.bold("\n Agent sVVV Balances"));
2523
4341
  for (const { agent, balance } of agentBalances) {
2524
4342
  const isMe = agent.toLowerCase() === account.address.toLowerCase();
2525
4343
  const label = isMe ? chalk2.green(`${agent} (you)`) : agent;
2526
- console.log(` ${label}: ${formatUnits2(balance, 18)} sVVV`);
4344
+ console.log(` ${label}: ${formatUnits3(balance, 18)} sVVV`);
2527
4345
  }
2528
4346
  console.log(chalk2.bold("\n Your Wallet"));
2529
- console.log(` sVVV: ${formatUnits2(mySvvv, 18)}`);
2530
- console.log(` Pending rewards: ${formatUnits2(myPending, 18)} VVV`);
2531
- console.log(` DIEM (credits): ${formatUnits2(myDiem, 18)}`);
4347
+ console.log(` sVVV: ${formatUnits3(mySvvv, 18)}`);
4348
+ console.log(` Pending rewards: ${formatUnits3(myPending, 18)} VVV`);
4349
+ console.log(` DIEM (credits): ${formatUnits3(myDiem, 18)}`);
2532
4350
  console.log(chalk2.dim(" (mint via `sherwood venice mint-diem --amount <sVVV>`)"));
2533
4351
  console.log(chalk2.bold("\n Venice API"));
2534
4352
  console.log(` Key: ${apiKey ? `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}` : chalk2.dim("not provisioned")}`);
@@ -2599,11 +4417,11 @@ ${opts.prompt}`;
2599
4417
  }
2600
4418
  try {
2601
4419
  const { createVeniceInferenceAttestation, getEasScanUrl: getEasScanUrl2 } = await import("./eas-5VIHTS3N.js");
2602
- const { keccak256: keccak2562, toHex, isAddress: isAddr } = await import("viem");
4420
+ const { keccak256: keccak2563, toHex, isAddress: isAddr } = await import("viem");
2603
4421
  const { getChainContracts: getChainContracts2 } = await import("./config-REASKDIK.js");
2604
4422
  const { getChain: getActiveChain } = await import("./network-3ZU7UBDU.js");
2605
4423
  const vaultRecipient = opts.vault && isAddr(opts.vault) ? opts.vault : getChainContracts2(getActiveChain().id).vault;
2606
- const promptHash = keccak2562(toHex(userContent)).slice(0, 18);
4424
+ const promptHash = keccak2563(toHex(userContent)).slice(0, 18);
2607
4425
  const { uid } = await createVeniceInferenceAttestation(
2608
4426
  result.model,
2609
4427
  result.usage.promptTokens,
@@ -2625,12 +4443,12 @@ ${opts.prompt}`;
2625
4443
  }
2626
4444
 
2627
4445
  // src/commands/allowance.ts
2628
- import { parseUnits as parseUnits4, formatUnits as formatUnits3, isAddress as isAddress3 } from "viem";
4446
+ import { parseUnits as parseUnits4, formatUnits as formatUnits4, isAddress as isAddress4 } from "viem";
2629
4447
  import chalk3 from "chalk";
2630
4448
  import ora3 from "ora";
2631
4449
 
2632
4450
  // src/strategies/allowance-disburse.ts
2633
- import { encodeFunctionData as encodeFunctionData9, parseUnits as parseUnits3 } from "viem";
4451
+ import { encodeFunctionData as encodeFunctionData16, parseUnits as parseUnits3 } from "viem";
2634
4452
  function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDecimals, minUsdc, swapPath) {
2635
4453
  const assetAmount = parseUnits3(config.amount, assetDecimals);
2636
4454
  const isUsdc = assetAddress.toLowerCase() === TOKENS().USDC.toLowerCase();
@@ -2639,7 +4457,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
2639
4457
  if (!isUsdc) {
2640
4458
  calls.push({
2641
4459
  target: assetAddress,
2642
- data: encodeFunctionData9({
4460
+ data: encodeFunctionData16({
2643
4461
  abi: ERC20_ABI,
2644
4462
  functionName: "approve",
2645
4463
  args: [UNISWAP().SWAP_ROUTER, assetAmount]
@@ -2649,7 +4467,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
2649
4467
  if (isWeth) {
2650
4468
  calls.push({
2651
4469
  target: UNISWAP().SWAP_ROUTER,
2652
- data: encodeFunctionData9({
4470
+ data: encodeFunctionData16({
2653
4471
  abi: SWAP_ROUTER_EXACT_INPUT_SINGLE_ABI,
2654
4472
  functionName: "exactInputSingle",
2655
4473
  args: [
@@ -2669,7 +4487,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
2669
4487
  } else {
2670
4488
  calls.push({
2671
4489
  target: UNISWAP().SWAP_ROUTER,
2672
- data: encodeFunctionData9({
4490
+ data: encodeFunctionData16({
2673
4491
  abi: SWAP_ROUTER_ABI,
2674
4492
  functionName: "exactInput",
2675
4493
  args: [
@@ -2689,7 +4507,7 @@ function buildDisburseBatch(config, vaultAddress, agents, assetAddress, assetDec
2689
4507
  for (const agent of agents) {
2690
4508
  calls.push({
2691
4509
  target: TOKENS().USDC,
2692
- data: encodeFunctionData9({
4510
+ data: encodeFunctionData16({
2693
4511
  abi: ERC20_ABI,
2694
4512
  functionName: "transfer",
2695
4513
  args: [agent, perAgent]
@@ -2706,7 +4524,7 @@ function registerAllowanceCommands(program2) {
2706
4524
  const allowance = program2.command("allowance").description("Disburse vault profits to agent wallets");
2707
4525
  allowance.command("disburse").description("Swap vault profits \u2192 USDC \u2192 distribute to all agent operator wallets").requiredOption("--vault <address>", "Vault address").requiredOption("--amount <amount>", "Deposit token amount to convert & distribute (e.g. 500)").option("--fee <tier>", "Fee tier for asset \u2192 USDC swap (500, 3000, 10000)", "3000").option("--slippage <bps>", "Slippage tolerance in bps", "100").option("--execute", "Execute on-chain (default: simulate only)", false).action(async (opts) => {
2708
4526
  const vaultAddress = opts.vault;
2709
- if (!isAddress3(vaultAddress)) {
4527
+ if (!isAddress4(vaultAddress)) {
2710
4528
  console.error(chalk3.red(`Invalid vault address: ${opts.vault}`));
2711
4529
  process.exit(1);
2712
4530
  }
@@ -2754,9 +4572,9 @@ function registerAllowanceCommands(program2) {
2754
4572
  console.log(chalk3.dim("\u2500".repeat(40)));
2755
4573
  console.log(` Asset: ${assetSymbol} (${assetDecimals} decimals)`);
2756
4574
  console.log(` Amount: ${opts.amount} ${assetSymbol}`);
2757
- console.log(` Vault balance: ${formatUnits3(assetBalance, assetDecimals)} ${assetSymbol}`);
2758
- console.log(` Deposited: ${formatUnits3(totalDeposited, assetDecimals)} ${assetSymbol}`);
2759
- console.log(` Profit: ${formatUnits3(profit, assetDecimals)} ${assetSymbol}`);
4575
+ console.log(` Vault balance: ${formatUnits4(assetBalance, assetDecimals)} ${assetSymbol}`);
4576
+ console.log(` Deposited: ${formatUnits4(totalDeposited, assetDecimals)} ${assetSymbol}`);
4577
+ console.log(` Profit: ${formatUnits4(profit, assetDecimals)} ${assetSymbol}`);
2760
4578
  console.log(` Agents: ${agents.length} (USDC will be split equally)`);
2761
4579
  if (!isUsdc) {
2762
4580
  console.log(` Routing: ${isWeth ? `WETH \u2192 USDC (fee ${fee})` : `${assetSymbol} \u2192 WETH \u2192 USDC (fee ${fee})`}`);
@@ -2765,7 +4583,7 @@ function registerAllowanceCommands(program2) {
2765
4583
  console.log(` Vault: ${vaultAddress}`);
2766
4584
  console.log();
2767
4585
  if (requestedAmount > profit) {
2768
- console.warn(chalk3.yellow(` Warning: amount (${opts.amount}) exceeds available profit (${formatUnits3(profit, assetDecimals)})`));
4586
+ console.warn(chalk3.yellow(` Warning: amount (${opts.amount}) exceeds available profit (${formatUnits4(profit, assetDecimals)})`));
2769
4587
  console.warn(chalk3.yellow(" This will use deposited capital, not just profits."));
2770
4588
  console.log();
2771
4589
  }
@@ -2799,7 +4617,7 @@ function registerAllowanceCommands(program2) {
2799
4617
  }
2800
4618
  minUsdc = applySlippage(amountOut, slippageBps);
2801
4619
  quoteSpinner.succeed(
2802
- `Quote: ${formatUnits3(amountOut, 6)} USDC (min: ${formatUnits3(minUsdc, 6)}, per agent: ${formatUnits3(minUsdc / BigInt(agents.length), 6)})`
4620
+ `Quote: ${formatUnits4(amountOut, 6)} USDC (min: ${formatUnits4(minUsdc, 6)}, per agent: ${formatUnits4(minUsdc / BigInt(agents.length), 6)})`
2803
4621
  );
2804
4622
  } catch (err) {
2805
4623
  quoteSpinner.fail("Failed to fetch quote");
@@ -2809,7 +4627,7 @@ function registerAllowanceCommands(program2) {
2809
4627
  }
2810
4628
  const perAgent = minUsdc / BigInt(agents.length);
2811
4629
  if (isUsdc) {
2812
- console.log(chalk3.dim(` Per agent: ${formatUnits3(perAgent, 6)} USDC`));
4630
+ console.log(chalk3.dim(` Per agent: ${formatUnits4(perAgent, 6)} USDC`));
2813
4631
  console.log();
2814
4632
  }
2815
4633
  const config = {
@@ -2840,7 +4658,7 @@ function registerAllowanceCommands(program2) {
2840
4658
  });
2841
4659
  allowance.command("status").description("Show vault profit and agent USDC balances").requiredOption("--vault <address>", "Vault address").action(async (opts) => {
2842
4660
  const vaultAddress = opts.vault;
2843
- if (!isAddress3(vaultAddress)) {
4661
+ if (!isAddress4(vaultAddress)) {
2844
4662
  console.error(chalk3.red(`Invalid vault address: ${opts.vault}`));
2845
4663
  process.exit(1);
2846
4664
  }
@@ -2876,14 +4694,14 @@ function registerAllowanceCommands(program2) {
2876
4694
  console.log(chalk3.dim("\u2500".repeat(50)));
2877
4695
  console.log(chalk3.bold("\n Vault"));
2878
4696
  console.log(` Asset: ${assetSymbol}`);
2879
- console.log(` Balance: ${formatUnits3(assetBalance, assetDecimals)} ${assetSymbol}`);
2880
- console.log(` Deposited: ${formatUnits3(totalDeposited, assetDecimals)} ${assetSymbol}`);
2881
- console.log(` Profit: ${formatUnits3(profit, assetDecimals)} ${assetSymbol}`);
4697
+ console.log(` Balance: ${formatUnits4(assetBalance, assetDecimals)} ${assetSymbol}`);
4698
+ console.log(` Deposited: ${formatUnits4(totalDeposited, assetDecimals)} ${assetSymbol}`);
4699
+ console.log(` Profit: ${formatUnits4(profit, assetDecimals)} ${assetSymbol}`);
2882
4700
  console.log(chalk3.bold("\n Agent USDC Balances"));
2883
4701
  for (const { agent, balance } of agentBalances) {
2884
4702
  const isMe = agent.toLowerCase() === account.address.toLowerCase();
2885
4703
  const label = isMe ? chalk3.green(`${agent} (you)`) : agent;
2886
- console.log(` ${label}: ${formatUnits3(balance, 6)} USDC`);
4704
+ console.log(` ${label}: ${formatUnits4(balance, 6)} USDC`);
2887
4705
  }
2888
4706
  console.log();
2889
4707
  } catch (err) {
@@ -2898,7 +4716,7 @@ function registerAllowanceCommands(program2) {
2898
4716
  import chalk4 from "chalk";
2899
4717
  import ora4 from "ora";
2900
4718
  import { SDK } from "agent0-sdk";
2901
- var IDENTITY_REGISTRY_ABI = [
4719
+ var IDENTITY_REGISTRY_ABI2 = [
2902
4720
  {
2903
4721
  name: "balanceOf",
2904
4722
  type: "function",
@@ -2931,6 +4749,19 @@ function getAgent0SDK() {
2931
4749
  function registerIdentityCommands(program2) {
2932
4750
  const identity = program2.command("identity").description("Manage ERC-8004 agent identity (via Agent0 SDK)");
2933
4751
  identity.command("mint").description("Register a new ERC-8004 agent identity (required before creating/joining syndicates)").requiredOption("--name <name>", "Agent name (e.g. 'Alpha Seeker Agent')").option("--description <desc>", "Agent description", "Sherwood syndicate agent").option("--image <uri>", "Agent image URI (IPFS recommended)").action(async (opts) => {
4752
+ if (isCalldataOnly()) {
4753
+ emitCalldata(
4754
+ encodeIdentityMint(
4755
+ {
4756
+ name: opts.name,
4757
+ description: opts.description,
4758
+ image: opts.image
4759
+ },
4760
+ getChain().id
4761
+ )
4762
+ );
4763
+ return;
4764
+ }
2934
4765
  const account = getAccount();
2935
4766
  const existingId = getAgentId();
2936
4767
  if (existingId) {
@@ -2978,7 +4809,7 @@ function registerIdentityCommands(program2) {
2978
4809
  try {
2979
4810
  const owner = await client.readContract({
2980
4811
  address: registry2,
2981
- abi: IDENTITY_REGISTRY_ABI,
4812
+ abi: IDENTITY_REGISTRY_ABI2,
2982
4813
  functionName: "ownerOf",
2983
4814
  args: [BigInt(tokenId)]
2984
4815
  });
@@ -3048,7 +4879,7 @@ function registerIdentityCommands(program2) {
3048
4879
  try {
3049
4880
  const balance = await client.readContract({
3050
4881
  address: registry2,
3051
- abi: IDENTITY_REGISTRY_ABI,
4882
+ abi: IDENTITY_REGISTRY_ABI2,
3052
4883
  functionName: "balanceOf",
3053
4884
  args: [account.address]
3054
4885
  });
@@ -3064,7 +4895,7 @@ function registerIdentityCommands(program2) {
3064
4895
  try {
3065
4896
  const owner = await client.readContract({
3066
4897
  address: registry2,
3067
- abi: IDENTITY_REGISTRY_ABI,
4898
+ abi: IDENTITY_REGISTRY_ABI2,
3068
4899
  functionName: "ownerOf",
3069
4900
  args: [BigInt(savedId)]
3070
4901
  });
@@ -3126,60 +4957,268 @@ function registerIdentityCommands(program2) {
3126
4957
  console.log(` Saved ID: ${chalk4.dim("none \u2014 run 'sherwood identity mint --name <name>'")}`);
3127
4958
  console.log();
3128
4959
  }
3129
- } catch (err) {
3130
- spinner.fail("Failed to check identity");
3131
- console.error(chalk4.red(formatContractError(err)));
3132
- process.exit(1);
4960
+ } catch (err) {
4961
+ spinner.fail("Failed to check identity");
4962
+ console.error(chalk4.red(formatContractError(err)));
4963
+ process.exit(1);
4964
+ }
4965
+ });
4966
+ }
4967
+
4968
+ // src/commands/proposal.ts
4969
+ import { isAddress as isAddress5, encodeFunctionData as encodeFunctionData18 } from "viem";
4970
+ import chalk6 from "chalk";
4971
+ import ora5 from "ora";
4972
+ import { readFileSync as readFileSync2 } from "fs";
4973
+
4974
+ // src/lib/format.ts
4975
+ function formatDurationShort(seconds) {
4976
+ const s = Number(seconds);
4977
+ if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)}d`;
4978
+ if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)}h`;
4979
+ if (s >= 60) return `${(s / 60).toFixed(0)}m`;
4980
+ return `${s}s`;
4981
+ }
4982
+ function formatDurationLong(seconds) {
4983
+ const s = Number(seconds);
4984
+ if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)} day${s >= 172800 ? "s" : ""}`;
4985
+ if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)} hour${s >= 7200 ? "s" : ""}`;
4986
+ if (s >= 60) return `${(s / 60).toFixed(0)} min`;
4987
+ return `${s}s`;
4988
+ }
4989
+ function formatShares(raw, decimals = 6) {
4990
+ const num = Number(raw) / 10 ** decimals;
4991
+ return num.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
4992
+ }
4993
+ function formatUSDC(raw) {
4994
+ const num = Number(raw) / 1e6;
4995
+ return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
4996
+ }
4997
+ function parseBigIntArg(value, name) {
4998
+ if (!/^-?\d+$/.test(value)) {
4999
+ throw new Error(`Invalid ${name}: "${value}" is not a valid integer`);
5000
+ }
5001
+ return BigInt(value);
5002
+ }
5003
+
5004
+ // src/lib/simulate.ts
5005
+ import { decodeFunctionData as decodeFunctionData2, encodeFunctionData as encodeFunctionData17 } from "viem";
5006
+ import chalk5 from "chalk";
5007
+
5008
+ // src/lib/risk.ts
5009
+ import { decodeFunctionData, erc20Abi as erc20Abi2 } from "viem";
5010
+ var MOONWELL_CTOKEN_ABI = [
5011
+ { name: "mint", type: "function", stateMutability: "nonpayable", inputs: [{ name: "mintAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
5012
+ { name: "redeem", type: "function", stateMutability: "nonpayable", inputs: [{ name: "redeemTokens", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
5013
+ { name: "redeemUnderlying", type: "function", stateMutability: "nonpayable", inputs: [{ name: "redeemAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
5014
+ { name: "borrow", type: "function", stateMutability: "nonpayable", inputs: [{ name: "borrowAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] },
5015
+ { name: "repayBorrow", type: "function", stateMutability: "nonpayable", inputs: [{ name: "repayAmount", type: "uint256" }], outputs: [{ name: "", type: "uint256" }] }
5016
+ ];
5017
+ var MOONWELL_COMPTROLLER_ABI = [
5018
+ { name: "enterMarkets", type: "function", stateMutability: "nonpayable", inputs: [{ name: "mTokens", type: "address[]" }], outputs: [{ name: "", type: "uint256[]" }] },
5019
+ { name: "exitMarket", type: "function", stateMutability: "nonpayable", inputs: [{ name: "mToken", type: "address" }], outputs: [{ name: "", type: "uint256" }] }
5020
+ ];
5021
+ var SWAP_ROUTER_SINGLE_ABI = [
5022
+ {
5023
+ name: "exactInputSingle",
5024
+ type: "function",
5025
+ stateMutability: "nonpayable",
5026
+ inputs: [{
5027
+ name: "params",
5028
+ type: "tuple",
5029
+ components: [
5030
+ { name: "tokenIn", type: "address" },
5031
+ { name: "tokenOut", type: "address" },
5032
+ { name: "fee", type: "uint24" },
5033
+ { name: "recipient", type: "address" },
5034
+ { name: "amountIn", type: "uint256" },
5035
+ { name: "amountOutMinimum", type: "uint256" },
5036
+ { name: "sqrtPriceLimitX96", type: "uint160" }
5037
+ ]
5038
+ }],
5039
+ outputs: [{ name: "amountOut", type: "uint256" }]
5040
+ }
5041
+ ];
5042
+ var SWAP_ROUTER_MULTI_ABI = [
5043
+ {
5044
+ name: "exactInput",
5045
+ type: "function",
5046
+ stateMutability: "nonpayable",
5047
+ inputs: [{
5048
+ name: "params",
5049
+ type: "tuple",
5050
+ components: [
5051
+ { name: "path", type: "bytes" },
5052
+ { name: "recipient", type: "address" },
5053
+ { name: "amountIn", type: "uint256" },
5054
+ { name: "amountOutMinimum", type: "uint256" }
5055
+ ]
5056
+ }],
5057
+ outputs: [{ name: "amountOut", type: "uint256" }]
5058
+ }
5059
+ ];
5060
+ function buildLabelMapFromKnown(known) {
5061
+ const map = /* @__PURE__ */ new Map();
5062
+ for (const [label, addr] of Object.entries(known)) {
5063
+ if (addr && addr !== "0x0000000000000000000000000000000000000000") {
5064
+ map.set(addr.toLowerCase(), label);
5065
+ }
5066
+ }
5067
+ return map;
5068
+ }
5069
+ function truncAddr(addr) {
5070
+ return `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
5071
+ }
5072
+ function labeledAddr(addr, labels) {
5073
+ const label = labels.get(addr.toLowerCase());
5074
+ return label ? `${label} (${truncAddr(addr)})` : truncAddr(addr);
5075
+ }
5076
+ function tryDecode(abi, data) {
5077
+ try {
5078
+ const decoded = decodeFunctionData({ abi, data });
5079
+ return { functionName: decoded.functionName, args: decoded.args ?? [] };
5080
+ } catch {
5081
+ return null;
5082
+ }
5083
+ }
5084
+ function decodeCallArgs(data) {
5085
+ return tryDecode(erc20Abi2, data) ?? tryDecode(MOONWELL_CTOKEN_ABI, data) ?? tryDecode(MOONWELL_COMPTROLLER_ABI, data) ?? tryDecode(SWAP_ROUTER_SINGLE_ABI, data) ?? // exactInputSingle (single-hop)
5086
+ tryDecode(SWAP_ROUTER_MULTI_ABI, data);
5087
+ }
5088
+ function analyzeRisk(calls, callResults, context) {
5089
+ const risks = [];
5090
+ const labels = buildLabelMapFromKnown(context?.knownAddresses ?? {});
5091
+ const vaultLower = context?.vault?.toLowerCase();
5092
+ const isKnown = (addr) => {
5093
+ const lower = addr.toLowerCase();
5094
+ return labels.has(lower) || lower === vaultLower;
5095
+ };
5096
+ let allTargetsVerified = calls.length > 0;
5097
+ let allCallsDecoded = calls.length > 0;
5098
+ for (let i = 0; i < calls.length; i++) {
5099
+ const call = calls[i];
5100
+ const result = callResults[i];
5101
+ const callNum = i + 1;
5102
+ if (result && !result.success) {
5103
+ risks.push({
5104
+ level: "critical",
5105
+ code: "SIMULATION_FAILED",
5106
+ message: `Call #${callNum} to ${labeledAddr(call.target, labels)} reverted during simulation`,
5107
+ callIndex: i
5108
+ });
5109
+ }
5110
+ const targetKnown = isKnown(call.target);
5111
+ if (!targetKnown) {
5112
+ allTargetsVerified = false;
5113
+ risks.push({
5114
+ level: "critical",
5115
+ code: "UNKNOWN_TARGET",
5116
+ message: `Call #${callNum} targets ${labeledAddr(call.target, labels)} which is not in the known address registry`,
5117
+ callIndex: i
5118
+ });
5119
+ }
5120
+ const decoded = decodeCallArgs(call.data);
5121
+ if (!decoded) {
5122
+ allCallsDecoded = false;
5123
+ if (!targetKnown) {
5124
+ risks.push({
5125
+ level: "critical",
5126
+ code: "UNDECODED_CALLDATA",
5127
+ message: `Call #${callNum} has unrecognized calldata (selector ${call.data.slice(0, 10)}) targeting unknown contract`,
5128
+ callIndex: i
5129
+ });
5130
+ }
5131
+ continue;
5132
+ }
5133
+ if (decoded.functionName === "transfer" && decoded.args.length >= 2) {
5134
+ const to = decoded.args[0];
5135
+ if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
5136
+ risks.push({
5137
+ level: "critical",
5138
+ code: "TRANSFER_TO_UNKNOWN",
5139
+ message: `Call #${callNum} transfer() sends funds to ${truncAddr(to)} which is not a known protocol`,
5140
+ callIndex: i
5141
+ });
5142
+ }
5143
+ }
5144
+ if (decoded.functionName === "transferFrom" && decoded.args.length >= 3) {
5145
+ const to = decoded.args[1];
5146
+ if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
5147
+ risks.push({
5148
+ level: "critical",
5149
+ code: "TRANSFER_FROM_TO_UNKNOWN",
5150
+ message: `Call #${callNum} transferFrom() sends funds to ${truncAddr(to)} which is not a known protocol`,
5151
+ callIndex: i
5152
+ });
5153
+ }
5154
+ }
5155
+ if (decoded.functionName === "approve" && decoded.args.length >= 2) {
5156
+ const spender = decoded.args[0];
5157
+ if (typeof spender === "string" && spender.startsWith("0x") && !isKnown(spender)) {
5158
+ risks.push({
5159
+ level: "critical",
5160
+ code: "APPROVE_TO_UNKNOWN",
5161
+ message: `Call #${callNum} approve() grants allowance to ${truncAddr(spender)} which is not a known protocol`,
5162
+ callIndex: i
5163
+ });
5164
+ }
3133
5165
  }
3134
- });
3135
- }
3136
-
3137
- // src/commands/proposal.ts
3138
- import { isAddress as isAddress4, encodeFunctionData as encodeFunctionData11 } from "viem";
3139
- import chalk6 from "chalk";
3140
- import ora5 from "ora";
3141
- import { readFileSync as readFileSync2 } from "fs";
3142
-
3143
- // src/lib/format.ts
3144
- function formatDurationShort(seconds) {
3145
- const s = Number(seconds);
3146
- if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)}d`;
3147
- if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)}h`;
3148
- if (s >= 60) return `${(s / 60).toFixed(0)}m`;
3149
- return `${s}s`;
3150
- }
3151
- function formatDurationLong(seconds) {
3152
- const s = Number(seconds);
3153
- if (s >= 86400) return `${(s / 86400).toFixed(s % 86400 === 0 ? 0 : 1)} day${s >= 172800 ? "s" : ""}`;
3154
- if (s >= 3600) return `${(s / 3600).toFixed(s % 3600 === 0 ? 0 : 1)} hour${s >= 7200 ? "s" : ""}`;
3155
- if (s >= 60) return `${(s / 60).toFixed(0)} min`;
3156
- return `${s}s`;
3157
- }
3158
- function formatShares(raw, decimals = 6) {
3159
- const num = Number(raw) / 10 ** decimals;
3160
- return num.toLocaleString("en-US", { minimumFractionDigits: 0, maximumFractionDigits: 2 });
3161
- }
3162
- function formatUSDC(raw) {
3163
- const num = Number(raw) / 1e6;
3164
- return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
3165
- }
3166
- function parseBigIntArg(value, name) {
3167
- if (!/^-?\d+$/.test(value)) {
3168
- throw new Error(`Invalid ${name}: "${value}" is not a valid integer`);
3169
5166
  }
3170
- return BigInt(value);
5167
+ if (context?.performanceFeeBps !== void 0) {
5168
+ const fee = context.performanceFeeBps;
5169
+ const maxFee = context.governorParams?.maxPerformanceFeeBps;
5170
+ if (maxFee !== void 0 && fee > maxFee * 80n / 100n) {
5171
+ risks.push({
5172
+ level: "critical",
5173
+ code: "EXCESSIVE_PERFORMANCE_FEE",
5174
+ message: `Performance fee ${Number(fee) / 100}% is within 20% of the hard cap (${Number(maxFee) / 100}%)`
5175
+ });
5176
+ } else if (fee > 2000n) {
5177
+ risks.push({
5178
+ level: "warning",
5179
+ code: "HIGH_PERFORMANCE_FEE",
5180
+ message: `Performance fee ${Number(fee) / 100}% exceeds 20% threshold`
5181
+ });
5182
+ }
5183
+ }
5184
+ if (context?.strategyDuration !== void 0) {
5185
+ const dur = context.strategyDuration;
5186
+ if (dur < 3600n) {
5187
+ risks.push({
5188
+ level: "warning",
5189
+ code: "SHORT_STRATEGY_DURATION",
5190
+ message: `Strategy duration ${Number(dur)}s is under 1 hour \u2014 flash-loan-style risk`
5191
+ });
5192
+ }
5193
+ if (dur > 2592000n) {
5194
+ risks.push({
5195
+ level: "warning",
5196
+ code: "LONG_STRATEGY_DURATION",
5197
+ message: `Strategy duration ${Number(dur) / 86400} days exceeds 30-day threshold`
5198
+ });
5199
+ }
5200
+ }
5201
+ const hasCritical = risks.some((r) => r.level === "critical");
5202
+ const hasWarning = risks.some((r) => r.level === "warning");
5203
+ if (!hasCritical && !hasWarning) {
5204
+ if (allTargetsVerified) {
5205
+ risks.push({ level: "info", code: "ALL_TARGETS_VERIFIED", message: "All call targets are verified protocol addresses" });
5206
+ }
5207
+ if (allCallsDecoded) {
5208
+ risks.push({ level: "info", code: "ALL_CALLS_DECODED", message: "All calldata successfully decoded" });
5209
+ }
5210
+ }
5211
+ return risks;
3171
5212
  }
3172
5213
 
3173
5214
  // src/lib/simulate.ts
3174
- import { decodeFunctionData, encodeFunctionData as encodeFunctionData10 } from "viem";
3175
- import chalk5 from "chalk";
3176
5215
  var G = chalk5.green;
3177
5216
  var W = chalk5.white;
3178
5217
  var DIM = chalk5.gray;
3179
5218
  var BOLD = chalk5.white.bold;
3180
5219
  var LABEL = chalk5.green.bold;
3181
5220
  var SEP = () => console.log(DIM("\u2500".repeat(60)));
3182
- var MOONWELL_CTOKEN_ABI = [
5221
+ var MOONWELL_CTOKEN_ABI2 = [
3183
5222
  {
3184
5223
  name: "mint",
3185
5224
  type: "function",
@@ -3216,7 +5255,7 @@ var MOONWELL_CTOKEN_ABI = [
3216
5255
  outputs: [{ name: "", type: "uint256" }]
3217
5256
  }
3218
5257
  ];
3219
- var MOONWELL_COMPTROLLER_ABI = [
5258
+ var MOONWELL_COMPTROLLER_ABI2 = [
3220
5259
  {
3221
5260
  name: "enterMarkets",
3222
5261
  type: "function",
@@ -3232,7 +5271,7 @@ var MOONWELL_COMPTROLLER_ABI = [
3232
5271
  outputs: [{ name: "", type: "uint256" }]
3233
5272
  }
3234
5273
  ];
3235
- var SWAP_ROUTER_SINGLE_ABI = [
5274
+ var SWAP_ROUTER_SINGLE_ABI2 = [
3236
5275
  {
3237
5276
  name: "exactInputSingle",
3238
5277
  type: "function",
@@ -3337,10 +5376,7 @@ function getAddressLabel(addr) {
3337
5376
  const labels = buildLabelMap();
3338
5377
  return labels.get(addr.toLowerCase()) ?? null;
3339
5378
  }
3340
- function truncAddr(addr) {
3341
- return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
3342
- }
3343
- function labeledAddr(addr) {
5379
+ function labeledAddr2(addr) {
3344
5380
  const label = buildLabelMap().get(addr.toLowerCase());
3345
5381
  const short = truncAddr(addr);
3346
5382
  return label ? `${short} (${label})` : short;
@@ -3378,21 +5414,21 @@ function decodeCallData(target, data) {
3378
5414
  if (mw) {
3379
5415
  const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
3380
5416
  if (isMToken) {
3381
- candidates.push({ abi: MOONWELL_CTOKEN_ABI, context: labels.get(targetLower) || "mToken" });
5417
+ candidates.push({ abi: MOONWELL_CTOKEN_ABI2, context: labels.get(targetLower) || "mToken" });
3382
5418
  }
3383
5419
  if (mw.COMPTROLLER.toLowerCase() === targetLower) {
3384
- candidates.push({ abi: MOONWELL_COMPTROLLER_ABI, context: "Comptroller" });
5420
+ candidates.push({ abi: MOONWELL_COMPTROLLER_ABI2, context: "Comptroller" });
3385
5421
  }
3386
5422
  }
3387
5423
  const uni = safeCall(() => UNISWAP());
3388
5424
  if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
3389
5425
  candidates.push({ abi: SWAP_ROUTER_ABI, context: "SwapRouter" });
3390
- candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI, context: "SwapRouter" });
5426
+ candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI2, context: "SwapRouter" });
3391
5427
  }
3392
5428
  candidates.push({ abi: ERC20_ABI, context: labels.get(targetLower) || "ERC20" });
3393
5429
  for (const { abi, context } of candidates) {
3394
5430
  try {
3395
- const decoded = decodeFunctionData({
5431
+ const decoded = decodeFunctionData2({
3396
5432
  abi,
3397
5433
  data
3398
5434
  });
@@ -3411,7 +5447,7 @@ function formatArgs(args, functionName, target) {
3411
5447
  if (typeof arg === "bigint") {
3412
5448
  parts.push(formatTokenAmount(arg, target));
3413
5449
  } else if (typeof arg === "string" && arg.startsWith("0x") && arg.length === 42) {
3414
- parts.push(labeledAddr(arg));
5450
+ parts.push(labeledAddr2(arg));
3415
5451
  } else if (typeof arg === "object" && arg !== null && !Array.isArray(arg)) {
3416
5452
  const entries = Object.entries(arg);
3417
5453
  const structParts = [];
@@ -3419,7 +5455,7 @@ function formatArgs(args, functionName, target) {
3419
5455
  if (typeof val === "bigint") {
3420
5456
  structParts.push(`${key}: ${formatTokenAmount(val, target)}`);
3421
5457
  } else if (typeof val === "string" && val.startsWith("0x") && val.length === 42) {
3422
- structParts.push(`${key}: ${labeledAddr(val)}`);
5458
+ structParts.push(`${key}: ${labeledAddr2(val)}`);
3423
5459
  } else if (typeof val === "string" && val.startsWith("0x") && key === "path") {
3424
5460
  structParts.push(`path: ${decodeUniswapPath(val)}`);
3425
5461
  } else {
@@ -3430,7 +5466,7 @@ function formatArgs(args, functionName, target) {
3430
5466
  } else if (Array.isArray(arg)) {
3431
5467
  const arrParts = arg.map((item) => {
3432
5468
  if (typeof item === "string" && item.startsWith("0x") && item.length === 42) {
3433
- return labeledAddr(item);
5469
+ return labeledAddr2(item);
3434
5470
  }
3435
5471
  return String(item);
3436
5472
  });
@@ -3462,168 +5498,9 @@ function decodeUniswapPath(pathHex) {
3462
5498
  function safeCall(fn) {
3463
5499
  try {
3464
5500
  return fn();
3465
- } catch {
3466
- return null;
3467
- }
3468
- }
3469
- function decodeCallArgs(target, data) {
3470
- if (data.length < 10) return null;
3471
- const targetLower = target.toLowerCase();
3472
- const candidates = [];
3473
- const mw = safeCall(() => MOONWELL());
3474
- if (mw) {
3475
- const isMToken = [mw.mUSDC, mw.mWETH, mw.mCbETH, mw.mWstETH, mw.mCbBTC, mw.mDAI, mw.mAERO].some((a) => a.toLowerCase() === targetLower);
3476
- if (isMToken) candidates.push({ abi: MOONWELL_CTOKEN_ABI });
3477
- if (mw.COMPTROLLER.toLowerCase() === targetLower) candidates.push({ abi: MOONWELL_COMPTROLLER_ABI });
3478
- }
3479
- const uni = safeCall(() => UNISWAP());
3480
- if (uni && uni.SWAP_ROUTER.toLowerCase() === targetLower) {
3481
- candidates.push({ abi: SWAP_ROUTER_ABI });
3482
- candidates.push({ abi: SWAP_ROUTER_SINGLE_ABI });
3483
- }
3484
- candidates.push({ abi: ERC20_ABI });
3485
- for (const { abi } of candidates) {
3486
- try {
3487
- const decoded = decodeFunctionData({ abi, data });
3488
- return { functionName: decoded.functionName, args: decoded.args };
3489
- } catch {
3490
- continue;
3491
- }
3492
- }
3493
- return null;
3494
- }
3495
- function analyzeRisk(calls, callResults, context) {
3496
- const risks = [];
3497
- const labels = buildLabelMap();
3498
- const vaultLower = context?.vault?.toLowerCase();
3499
- const isKnown = (addr) => {
3500
- const lower = addr.toLowerCase();
3501
- return labels.has(lower) || lower === vaultLower;
3502
- };
3503
- let allTargetsVerified = true;
3504
- let allCallsDecoded = true;
3505
- for (let i = 0; i < calls.length; i++) {
3506
- const call = calls[i];
3507
- const result = callResults[i];
3508
- const callNum = i + 1;
3509
- if (result && !result.success) {
3510
- risks.push({
3511
- level: "critical",
3512
- code: "SIMULATION_FAILED",
3513
- message: `Call #${callNum} to ${labeledAddr(call.target)} reverted during simulation`,
3514
- callIndex: i
3515
- });
3516
- }
3517
- const targetKnown = isKnown(call.target);
3518
- if (!targetKnown) {
3519
- allTargetsVerified = false;
3520
- risks.push({
3521
- level: "critical",
3522
- code: "UNKNOWN_TARGET",
3523
- message: `Call #${callNum} targets ${labeledAddr(call.target)} which is not in the known address registry`,
3524
- callIndex: i
3525
- });
3526
- }
3527
- const decoded = decodeCallArgs(call.target, call.data);
3528
- if (!decoded) {
3529
- allCallsDecoded = false;
3530
- if (!targetKnown) {
3531
- risks.push({
3532
- level: "critical",
3533
- code: "UNDECODED_CALLDATA",
3534
- message: `Call #${callNum} has unrecognized calldata (selector ${call.data.slice(0, 10)}) targeting unknown contract`,
3535
- callIndex: i
3536
- });
3537
- }
3538
- continue;
3539
- }
3540
- if (decoded.functionName === "transfer" && decoded.args.length >= 2) {
3541
- const to = decoded.args[0];
3542
- if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
3543
- risks.push({
3544
- level: "critical",
3545
- code: "TRANSFER_TO_UNKNOWN",
3546
- message: `Call #${callNum} transfer() sends funds to ${truncAddr(to)} which is not a known protocol`,
3547
- callIndex: i
3548
- });
3549
- }
3550
- }
3551
- if (decoded.functionName === "transferFrom" && decoded.args.length >= 3) {
3552
- const to = decoded.args[1];
3553
- if (typeof to === "string" && to.startsWith("0x") && !isKnown(to)) {
3554
- risks.push({
3555
- level: "critical",
3556
- code: "TRANSFER_FROM_TO_UNKNOWN",
3557
- message: `Call #${callNum} transferFrom() sends funds to ${truncAddr(to)} which is not a known protocol`,
3558
- callIndex: i
3559
- });
3560
- }
3561
- }
3562
- if (decoded.functionName === "approve" && decoded.args.length >= 2) {
3563
- const spender = decoded.args[0];
3564
- if (typeof spender === "string" && spender.startsWith("0x") && !isKnown(spender)) {
3565
- risks.push({
3566
- level: "critical",
3567
- code: "APPROVE_TO_UNKNOWN",
3568
- message: `Call #${callNum} approve() grants allowance to ${truncAddr(spender)} which is not a known protocol`,
3569
- callIndex: i
3570
- });
3571
- }
3572
- }
3573
- }
3574
- if (context?.performanceFeeBps !== void 0) {
3575
- const fee = context.performanceFeeBps;
3576
- const maxFee = context.governorParams?.maxPerformanceFeeBps;
3577
- if (maxFee && fee > maxFee * 80n / 100n) {
3578
- risks.push({
3579
- level: "critical",
3580
- code: "EXCESSIVE_PERFORMANCE_FEE",
3581
- message: `Performance fee ${Number(fee) / 100}% is within 20% of the hard cap (${Number(maxFee) / 100}%)`
3582
- });
3583
- } else if (fee > 2000n) {
3584
- risks.push({
3585
- level: "warning",
3586
- code: "HIGH_PERFORMANCE_FEE",
3587
- message: `Performance fee ${Number(fee) / 100}% exceeds 20% threshold`
3588
- });
3589
- }
3590
- }
3591
- if (context?.strategyDuration !== void 0) {
3592
- const dur = context.strategyDuration;
3593
- if (dur < 3600n) {
3594
- risks.push({
3595
- level: "warning",
3596
- code: "SHORT_STRATEGY_DURATION",
3597
- message: `Strategy duration ${Number(dur)}s is under 1 hour \u2014 flash-loan-style risk`
3598
- });
3599
- }
3600
- if (dur > 2592000n) {
3601
- risks.push({
3602
- level: "warning",
3603
- code: "LONG_STRATEGY_DURATION",
3604
- message: `Strategy duration ${Number(dur) / 86400} days exceeds 30-day threshold`
3605
- });
3606
- }
3607
- }
3608
- const hasCritical = risks.some((r) => r.level === "critical");
3609
- const hasWarning = risks.some((r) => r.level === "warning");
3610
- if (!hasCritical && !hasWarning) {
3611
- if (allTargetsVerified) {
3612
- risks.push({
3613
- level: "info",
3614
- code: "ALL_TARGETS_VERIFIED",
3615
- message: "All call targets are verified protocol addresses"
3616
- });
3617
- }
3618
- if (allCallsDecoded) {
3619
- risks.push({
3620
- level: "info",
3621
- code: "ALL_CALLS_DECODED",
3622
- message: "All calldata successfully decoded"
3623
- });
3624
- }
5501
+ } catch {
5502
+ return null;
3625
5503
  }
3626
- return risks;
3627
5504
  }
3628
5505
  var API_URLS = {
3629
5506
  "base": "https://app.sherwood.sh",
@@ -3674,7 +5551,7 @@ async function simulateViaApi(vault, calls) {
3674
5551
  const warnings = [];
3675
5552
  for (const cr of callResults) {
3676
5553
  if (!cr.success) {
3677
- warnings.push(`Call #${cr.index + 1} to ${labeledAddr(cr.target)} failed`);
5554
+ warnings.push(`Call #${cr.index + 1} to ${labeledAddr2(cr.target)} failed`);
3678
5555
  }
3679
5556
  }
3680
5557
  return {
@@ -3690,7 +5567,7 @@ async function simulateViaApi(vault, calls) {
3690
5567
  async function simulateViaEthCall(vault, calls) {
3691
5568
  const governor = getGovernorAddress();
3692
5569
  const client = getPublicClient();
3693
- const input2 = encodeFunctionData10({
5570
+ const input2 = encodeFunctionData17({
3694
5571
  abi: SYNDICATE_VAULT_ABI,
3695
5572
  functionName: "executeGovernorBatch",
3696
5573
  args: [calls.map((c) => ({ target: c.target, data: c.data, value: c.value }))]
@@ -3743,7 +5620,12 @@ async function simulateBatchCalls(vault, calls, callType, riskContext) {
3743
5620
  console.log(DIM(` Simulation API unavailable (${message}), falling back to eth_call...`));
3744
5621
  result = await simulateViaEthCall(vault, calls);
3745
5622
  }
3746
- const ctx = { vault, ...riskContext };
5623
+ const inlineLabels = buildLabelMap();
5624
+ const knownAddresses = {};
5625
+ for (const [addr, label] of inlineLabels) {
5626
+ knownAddresses[label] = addr;
5627
+ }
5628
+ const ctx = { vault, ...riskContext, knownAddresses };
3747
5629
  result.risks = analyzeRisk(calls, result.callResults, ctx);
3748
5630
  return result;
3749
5631
  }
@@ -3916,10 +5798,10 @@ function parseCallsFile(path) {
3916
5798
  }
3917
5799
  function registerProposalCommands(program2) {
3918
5800
  const proposal = program2.command("proposal").description("Governance proposals \u2014 create, vote, execute, settle");
3919
- proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").option("--simulate", "Simulate calls via Tenderly before submitting").option("--force", "Submit even if simulation fails (requires --simulate)").action(async (opts) => {
5801
+ proposal.command("create").description("Submit a strategy proposal").requiredOption("--vault <address>", "Vault address the proposal targets").requiredOption("--name <name>", "Strategy name").requiredOption("--description <text>", "Strategy rationale and risk summary").requiredOption("--performance-fee <bps>", "Agent fee in bps (e.g. 1500 = 15%)").requiredOption("--duration <duration>", "Strategy duration (e.g. 7d, 24h, 3600)").requiredOption("--execute-calls <path>", "Path to JSON file with execute Call[] array").requiredOption("--settle-calls <path>", "Path to JSON file with settlement Call[] array").option("--strategy <address>", "Live-NAV strategy clone address (default: none \u2014 queue-only)").option("--metadata-uri <uri>", "Override \u2014 skip IPFS upload and use this URI directly").option("--simulate", "Simulate calls via Tenderly before submitting").option("--force", "Submit even if simulation fails (requires --simulate)").action(async (opts) => {
3920
5802
  try {
3921
5803
  const vault = opts.vault;
3922
- if (!isAddress4(vault)) {
5804
+ if (!isAddress5(vault)) {
3923
5805
  console.error(chalk6.red(`Invalid vault address: ${opts.vault}`));
3924
5806
  process.exit(1);
3925
5807
  }
@@ -3927,6 +5809,45 @@ function registerProposalCommands(program2) {
3927
5809
  const strategyDuration = parseDuration(opts.duration);
3928
5810
  const executeCalls = parseCallsFile(opts.executeCalls);
3929
5811
  const settleCalls = parseCallsFile(opts.settleCalls);
5812
+ const ZERO_STRATEGY = "0x0000000000000000000000000000000000000000";
5813
+ let strategy2 = ZERO_STRATEGY;
5814
+ if (opts.strategy) {
5815
+ if (!isAddress5(opts.strategy)) {
5816
+ console.error(chalk6.red(`Invalid strategy address: ${opts.strategy}`));
5817
+ process.exit(1);
5818
+ }
5819
+ strategy2 = opts.strategy;
5820
+ }
5821
+ if (isCalldataOnly()) {
5822
+ if (!opts.metadataUri) {
5823
+ console.error(
5824
+ chalk6.red(
5825
+ "--calldata-only requires --metadata-uri (IPFS metadata upload needs a signing wallet)"
5826
+ )
5827
+ );
5828
+ process.exit(1);
5829
+ }
5830
+ try {
5831
+ emitCalldata(
5832
+ encodePropose(
5833
+ {
5834
+ vault,
5835
+ strategy: strategy2,
5836
+ metadataURI: opts.metadataUri,
5837
+ performanceFeeBps: Number(performanceFeeBps),
5838
+ strategyDuration,
5839
+ executeCalls,
5840
+ settlementCalls: settleCalls
5841
+ },
5842
+ getChain().id
5843
+ )
5844
+ );
5845
+ } catch (err) {
5846
+ console.error(chalk6.red(err instanceof Error ? err.message : String(err)));
5847
+ process.exit(1);
5848
+ }
5849
+ return;
5850
+ }
3930
5851
  let metadataURI = opts.metadataUri || "";
3931
5852
  if (!metadataURI) {
3932
5853
  const spinner2 = ora5({ text: W2("Uploading metadata to IPFS..."), color: "green" }).start();
@@ -4001,7 +5922,7 @@ function registerProposalCommands(program2) {
4001
5922
  const spinner = ora5({ text: W2("Submitting proposal..."), color: "green" }).start();
4002
5923
  const result = await propose(
4003
5924
  vault,
4004
- "0x0000000000000000000000000000000000000000",
5925
+ strategy2,
4005
5926
  metadataURI,
4006
5927
  performanceFeeBps,
4007
5928
  strategyDuration,
@@ -4157,6 +6078,11 @@ function registerProposalCommands(program2) {
4157
6078
  console.error(chalk6.red(`Invalid support value "${opts.support}". Use for|against|abstain.`));
4158
6079
  process.exit(1);
4159
6080
  }
6081
+ if (isCalldataOnly()) {
6082
+ const voteDir = support === VOTE_TYPE.For ? "For" : support === VOTE_TYPE.Against ? "Against" : "Abstain";
6083
+ emitCalldata(encodeVote({ proposalId, vote: voteDir }, getChain().id));
6084
+ return;
6085
+ }
4160
6086
  const account = getAccount();
4161
6087
  const spinner = ora5("Loading proposal...").start();
4162
6088
  const p = await getProposal(proposalId);
@@ -4196,6 +6122,10 @@ function registerProposalCommands(program2) {
4196
6122
  proposal.command("execute").description("Execute an approved proposal").requiredOption("--id <proposalId>", "Proposal ID").option("--dry-run", "Simulate execution without sending a transaction").action(async (opts) => {
4197
6123
  try {
4198
6124
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
6125
+ if (isCalldataOnly()) {
6126
+ emitCalldata(encodeExecute({ proposalId }, getChain().id));
6127
+ return;
6128
+ }
4199
6129
  const spinner = ora5("Loading proposal...").start();
4200
6130
  const state = await getProposalState(proposalId);
4201
6131
  if (state !== PROPOSAL_STATE.Approved) {
@@ -4236,6 +6166,17 @@ function registerProposalCommands(program2) {
4236
6166
  proposal.command("settle").description("Settle an executed proposal (auto-routes settlement path)").requiredOption("--id <proposalId>", "Proposal ID").option("--calls <path>", "Path to JSON file with settle Call[] (for agent/emergency settle)").action(async (opts) => {
4237
6167
  try {
4238
6168
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
6169
+ if (isCalldataOnly()) {
6170
+ if (opts.calls) {
6171
+ const calls = parseCallsFile(opts.calls);
6172
+ emitCalldata(
6173
+ encodeEmergencySettle({ proposalId, calls }, getChain().id)
6174
+ );
6175
+ } else {
6176
+ emitCalldata(encodeSettle({ proposalId }, getChain().id));
6177
+ }
6178
+ return;
6179
+ }
4239
6180
  const account = getAccount();
4240
6181
  const spinner = ora5("Loading proposal...").start();
4241
6182
  const p = await getProposal(proposalId);
@@ -4281,6 +6222,10 @@ function registerProposalCommands(program2) {
4281
6222
  ).action(async (opts) => {
4282
6223
  try {
4283
6224
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
6225
+ if (isCalldataOnly()) {
6226
+ emitCalldata(encodeUnstick({ proposalId }, getChain().id));
6227
+ return;
6228
+ }
4284
6229
  const account = getAccount();
4285
6230
  const spinner = ora5("Loading proposal...").start();
4286
6231
  const p = await getProposal(proposalId);
@@ -4330,7 +6275,7 @@ function registerProposalCommands(program2) {
4330
6275
  process.exit(1);
4331
6276
  }
4332
6277
  const asset = await getAssetAddress();
4333
- const noopCalldata = encodeFunctionData11({
6278
+ const noopCalldata = encodeFunctionData18({
4334
6279
  abi: ERC20_ABI,
4335
6280
  functionName: "balanceOf",
4336
6281
  args: [vault]
@@ -4400,6 +6345,10 @@ function registerProposalCommands(program2) {
4400
6345
  proposal.command("cancel").description("Cancel a proposal (proposer or vault owner)").requiredOption("--id <proposalId>", "Proposal ID").option("--emergency", "Emergency cancel (vault owner only, any non-settled state)").action(async (opts) => {
4401
6346
  try {
4402
6347
  const proposalId = parseBigIntArg(opts.id, "proposal ID");
6348
+ if (isCalldataOnly()) {
6349
+ emitCalldata(encodeCancel({ proposalId }, getChain().id));
6350
+ return;
6351
+ }
4403
6352
  const spinner = ora5("Loading proposal...").start();
4404
6353
  const state = await getProposalState(proposalId);
4405
6354
  if (state === PROPOSAL_STATE.Settled || state === PROPOSAL_STATE.Cancelled) {
@@ -4474,7 +6423,7 @@ function registerProposalCommands(program2) {
4474
6423
  console.log(DIM2(`
4475
6424
  Simulating proposal #${proposalId} on vault ${vault.slice(0, 10)}...`));
4476
6425
  } else if (opts.executeCalls) {
4477
- if (!opts.vault || !isAddress4(opts.vault)) {
6426
+ if (!opts.vault || !isAddress5(opts.vault)) {
4478
6427
  console.error(chalk6.red("\n \u2716 --vault is required when using --execute-calls"));
4479
6428
  process.exit(1);
4480
6429
  }
@@ -4525,6 +6474,33 @@ var DIM3 = chalk7.gray;
4525
6474
  var BOLD3 = chalk7.white.bold;
4526
6475
  var LABEL3 = chalk7.green.bold;
4527
6476
  var SEP3 = () => console.log(DIM3("\u2500".repeat(60)));
6477
+ function registerParamSetter(governor, spec) {
6478
+ governor.command(spec.command).description(spec.description).requiredOption(spec.flag, spec.flagDescription).action(async (opts) => {
6479
+ const rawValue = String(opts[spec.valueLabel]);
6480
+ if (isCalldataOnly()) {
6481
+ try {
6482
+ const value = parseBigIntArg(rawValue, spec.valueLabel);
6483
+ emitCalldata(
6484
+ encodeGovernorParam({ param: spec.param, value }, getChain().id)
6485
+ );
6486
+ } catch (err) {
6487
+ console.error(chalk7.red(formatContractError(err)));
6488
+ process.exit(1);
6489
+ }
6490
+ return;
6491
+ }
6492
+ const spinner = ora6(spec.spinnerText).start();
6493
+ try {
6494
+ const hash = await spec.setter(parseBigIntArg(rawValue, spec.valueLabel));
6495
+ spinner.succeed(G3(spec.succeed(rawValue)));
6496
+ console.log(DIM3(` ${getExplorerUrl(hash)}`));
6497
+ } catch (err) {
6498
+ spinner.fail(spec.failText);
6499
+ console.error(chalk7.red(formatContractError(err)));
6500
+ process.exit(1);
6501
+ }
6502
+ });
6503
+ }
4528
6504
  function registerGovernorCommands(program2) {
4529
6505
  const governor = program2.command("governor").description("Governor parameters and vault management");
4530
6506
  governor.command("info").description("Display current governor parameters and registered vaults").action(async () => {
@@ -4562,96 +6538,96 @@ function registerGovernorCommands(program2) {
4562
6538
  process.exit(1);
4563
6539
  }
4564
6540
  });
4565
- governor.command("set-voting-period").description("Set the voting period (owner only)").requiredOption("--seconds <n>", "New voting period in seconds").action(async (opts) => {
4566
- const spinner = ora6("Setting voting period...").start();
4567
- try {
4568
- const hash = await setVotingPeriod(parseBigIntArg(opts.seconds, "seconds"));
4569
- spinner.succeed(G3(`Voting period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4570
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4571
- } catch (err) {
4572
- spinner.fail("Failed to set voting period");
4573
- console.error(chalk7.red(formatContractError(err)));
4574
- process.exit(1);
4575
- }
6541
+ registerParamSetter(governor, {
6542
+ command: "set-voting-period",
6543
+ description: "Set the voting period (owner only)",
6544
+ flag: "--seconds <n>",
6545
+ flagDescription: "New voting period in seconds",
6546
+ valueLabel: "seconds",
6547
+ param: "votingPeriod",
6548
+ setter: setVotingPeriod,
6549
+ spinnerText: "Setting voting period...",
6550
+ succeed: (v) => `Voting period updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6551
+ failText: "Failed to set voting period"
4576
6552
  });
4577
- governor.command("set-execution-window").description("Set the execution window (owner only)").requiredOption("--seconds <n>", "New execution window in seconds").action(async (opts) => {
4578
- const spinner = ora6("Setting execution window...").start();
4579
- try {
4580
- const hash = await setExecutionWindow(parseBigIntArg(opts.seconds, "seconds"));
4581
- spinner.succeed(G3(`Execution window change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4582
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4583
- } catch (err) {
4584
- spinner.fail("Failed to set execution window");
4585
- console.error(chalk7.red(formatContractError(err)));
4586
- process.exit(1);
4587
- }
6553
+ registerParamSetter(governor, {
6554
+ command: "set-execution-window",
6555
+ description: "Set the execution window (owner only)",
6556
+ flag: "--seconds <n>",
6557
+ flagDescription: "New execution window in seconds",
6558
+ valueLabel: "seconds",
6559
+ param: "executionWindow",
6560
+ setter: setExecutionWindow,
6561
+ spinnerText: "Setting execution window...",
6562
+ succeed: (v) => `Execution window updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6563
+ failText: "Failed to set execution window"
4588
6564
  });
4589
- governor.command("set-veto-threshold").description("Set the veto threshold in bps (owner only)").requiredOption("--bps <n>", "New veto threshold in bps (e.g. 4000 = 40%)").action(async (opts) => {
4590
- const spinner = ora6("Setting veto threshold...").start();
4591
- try {
4592
- const hash = await setVetoThresholdBps(parseBigIntArg(opts.bps, "bps"));
4593
- spinner.succeed(G3(`Veto threshold change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4594
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4595
- } catch (err) {
4596
- spinner.fail("Failed to set veto threshold");
4597
- console.error(chalk7.red(formatContractError(err)));
4598
- process.exit(1);
4599
- }
6565
+ registerParamSetter(governor, {
6566
+ command: "set-veto-threshold",
6567
+ description: "Set the veto threshold in bps (owner only)",
6568
+ flag: "--bps <n>",
6569
+ flagDescription: "New veto threshold in bps (e.g. 4000 = 40%)",
6570
+ valueLabel: "bps",
6571
+ param: "vetoThresholdBps",
6572
+ setter: setVetoThresholdBps,
6573
+ spinnerText: "Setting veto threshold...",
6574
+ succeed: (v) => `Veto threshold updated to ${Number(v) / 100}% (owner-instant \u2014 the owner multisig enforces its own delay).`,
6575
+ failText: "Failed to set veto threshold"
4600
6576
  });
4601
- governor.command("set-max-fee").description("Set the max performance fee in bps (owner only)").requiredOption("--bps <n>", "New max fee in bps (e.g. 3000 = 30%)").action(async (opts) => {
4602
- const spinner = ora6("Setting max fee...").start();
4603
- try {
4604
- const hash = await setMaxPerformanceFeeBps(parseBigIntArg(opts.bps, "bps"));
4605
- spinner.succeed(G3(`Max performance fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4606
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4607
- } catch (err) {
4608
- spinner.fail("Failed to set max fee");
4609
- console.error(chalk7.red(formatContractError(err)));
4610
- process.exit(1);
4611
- }
6577
+ registerParamSetter(governor, {
6578
+ command: "set-max-fee",
6579
+ description: "Set the max performance fee in bps (owner only)",
6580
+ flag: "--bps <n>",
6581
+ flagDescription: "New max fee in bps (e.g. 3000 = 30%)",
6582
+ valueLabel: "bps",
6583
+ param: "maxPerformanceFeeBps",
6584
+ setter: setMaxPerformanceFeeBps,
6585
+ spinnerText: "Setting max fee...",
6586
+ succeed: (v) => `Max performance fee updated to ${Number(v) / 100}% (owner-instant \u2014 the owner multisig enforces its own delay).`,
6587
+ failText: "Failed to set max fee"
4612
6588
  });
4613
- governor.command("set-max-duration").description("Set the max strategy duration in seconds (owner only)").requiredOption("--seconds <n>", "New max duration in seconds").action(async (opts) => {
4614
- const spinner = ora6("Setting max duration...").start();
4615
- try {
4616
- const hash = await setMaxStrategyDuration(parseBigIntArg(opts.seconds, "seconds"));
4617
- spinner.succeed(G3(`Max strategy duration change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4618
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4619
- } catch (err) {
4620
- spinner.fail("Failed to set max duration");
4621
- console.error(chalk7.red(formatContractError(err)));
4622
- process.exit(1);
4623
- }
6589
+ registerParamSetter(governor, {
6590
+ command: "set-max-duration",
6591
+ description: "Set the max strategy duration in seconds (owner only)",
6592
+ flag: "--seconds <n>",
6593
+ flagDescription: "New max duration in seconds",
6594
+ valueLabel: "seconds",
6595
+ param: "maxStrategyDuration",
6596
+ setter: setMaxStrategyDuration,
6597
+ spinnerText: "Setting max duration...",
6598
+ succeed: (v) => `Max strategy duration updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6599
+ failText: "Failed to set max duration"
4624
6600
  });
4625
- governor.command("set-cooldown").description("Set the cooldown period in seconds (owner only)").requiredOption("--seconds <n>", "New cooldown in seconds").action(async (opts) => {
4626
- const spinner = ora6("Setting cooldown...").start();
4627
- try {
4628
- const hash = await setCooldownPeriod(parseBigIntArg(opts.seconds, "seconds"));
4629
- spinner.succeed(G3(`Cooldown period change queued (${opts.seconds}s). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4630
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4631
- } catch (err) {
4632
- spinner.fail("Failed to set cooldown");
4633
- console.error(chalk7.red(formatContractError(err)));
4634
- process.exit(1);
4635
- }
6601
+ registerParamSetter(governor, {
6602
+ command: "set-cooldown",
6603
+ description: "Set the cooldown period in seconds (owner only)",
6604
+ flag: "--seconds <n>",
6605
+ flagDescription: "New cooldown in seconds",
6606
+ valueLabel: "seconds",
6607
+ param: "cooldownPeriod",
6608
+ setter: setCooldownPeriod,
6609
+ spinnerText: "Setting cooldown...",
6610
+ succeed: (v) => `Cooldown period updated to ${v}s (owner-instant \u2014 the owner multisig enforces its own delay).`,
6611
+ failText: "Failed to set cooldown"
4636
6612
  });
4637
- governor.command("set-protocol-fee").description("Set the protocol fee in bps (owner only)").requiredOption("--bps <n>", "New protocol fee in bps (e.g. 500 = 5%, max 1000 = 10%)").action(async (opts) => {
4638
- const spinner = ora6("Setting protocol fee...").start();
4639
- try {
4640
- const hash = await setProtocolFeeBps(parseBigIntArg(opts.bps, "bps"));
4641
- spinner.succeed(G3(`Protocol fee change queued (${Number(opts.bps) / 100}%). Finalize after the timelock delay with \`sherwood governor finalize-param\`.`));
4642
- console.log(DIM3(` ${getExplorerUrl(hash)}`));
4643
- } catch (err) {
4644
- spinner.fail("Failed to set protocol fee");
4645
- console.error(chalk7.red(formatContractError(err)));
4646
- process.exit(1);
4647
- }
6613
+ registerParamSetter(governor, {
6614
+ command: "set-protocol-fee",
6615
+ description: "Set the protocol fee in bps (owner only)",
6616
+ flag: "--bps <n>",
6617
+ flagDescription: "New protocol fee in bps (e.g. 500 = 5%, max 1000 = 10%)",
6618
+ valueLabel: "bps",
6619
+ param: "protocolFeeBps",
6620
+ setter: setProtocolFeeBps,
6621
+ spinnerText: "Setting protocol fee...",
6622
+ succeed: (v) => `Protocol fee updated to ${Number(v) / 100}% (owner-instant \u2014 the owner multisig enforces its own delay).`,
6623
+ failText: "Failed to set protocol fee"
4648
6624
  });
4649
6625
  }
4650
6626
 
4651
6627
  // src/commands/guardian-delegate.ts
4652
6628
  import chalk8 from "chalk";
4653
6629
  import ora7 from "ora";
4654
- import { formatUnits as formatUnits4, isAddress as isAddress5, parseUnits as parseUnits5 } from "viem";
6630
+ import { formatUnits as formatUnits5, isAddress as isAddress6, parseUnits as parseUnits5 } from "viem";
4655
6631
  var G4 = chalk8.green;
4656
6632
  var W4 = chalk8.white;
4657
6633
  var DIM4 = chalk8.gray;
@@ -4659,7 +6635,7 @@ var BOLD4 = chalk8.white.bold;
4659
6635
  var LABEL4 = chalk8.green.bold;
4660
6636
  var WARN = chalk8.yellow;
4661
6637
  var SEP4 = () => console.log(DIM4("\u2500".repeat(60)));
4662
- var GUARDIAN_REGISTRY_ABI = [
6638
+ var GUARDIAN_REGISTRY_ABI2 = [
4663
6639
  // Guardian stake
4664
6640
  { type: "function", name: "stakeAsGuardian", inputs: [{ type: "uint256" }, { type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
4665
6641
  { type: "function", name: "requestUnstakeGuardian", inputs: [], outputs: [], stateMutability: "nonpayable" },
@@ -4682,7 +6658,7 @@ var GUARDIAN_REGISTRY_ABI = [
4682
6658
  { type: "function", name: "claimProposalReward", inputs: [{ type: "uint256" }], outputs: [], stateMutability: "nonpayable" },
4683
6659
  { type: "function", name: "claimDelegatorProposalReward", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [], stateMutability: "nonpayable" }
4684
6660
  ];
4685
- var ERC20_ABI2 = [
6661
+ var ERC20_ABI3 = [
4686
6662
  { type: "function", name: "approve", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }], stateMutability: "nonpayable" },
4687
6663
  { type: "function", name: "balanceOf", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
4688
6664
  { type: "function", name: "decimals", inputs: [], outputs: [{ type: "uint8" }], stateMutability: "view" }
@@ -4715,7 +6691,7 @@ async function ensureWoodAllowance(amount) {
4715
6691
  try {
4716
6692
  const hash = await wallet.writeContract({
4717
6693
  address: wood(),
4718
- abi: ERC20_ABI2,
6694
+ abi: ERC20_ABI3,
4719
6695
  functionName: "approve",
4720
6696
  args: [registry(), 2n ** 256n - 1n],
4721
6697
  chain: null,
@@ -4734,7 +6710,7 @@ function registerGuardianCommands(program2) {
4734
6710
  const pc = getPublicClient();
4735
6711
  let target;
4736
6712
  if (addressArg) {
4737
- if (!isAddress5(addressArg)) {
6713
+ if (!isAddress6(addressArg)) {
4738
6714
  console.error(chalk8.red("Invalid address"));
4739
6715
  process.exit(1);
4740
6716
  }
@@ -4746,13 +6722,13 @@ function registerGuardianCommands(program2) {
4746
6722
  const spinner = ora7("Loading...").start();
4747
6723
  try {
4748
6724
  const [stake, active, commission] = await Promise.all([
4749
- pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "guardianStake", args: [target] }),
4750
- pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "isActiveGuardian", args: [target] }),
4751
- pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "commissionOf", args: [target] })
6725
+ pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "guardianStake", args: [target] }),
6726
+ pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "isActiveGuardian", args: [target] }),
6727
+ pc.readContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "commissionOf", args: [target] })
4752
6728
  ]);
4753
6729
  const delegatedIn = await pc.readContract({
4754
6730
  address: registry(),
4755
- abi: GUARDIAN_REGISTRY_ABI,
6731
+ abi: GUARDIAN_REGISTRY_ABI2,
4756
6732
  functionName: "delegatedInbound",
4757
6733
  args: [target]
4758
6734
  });
@@ -4762,8 +6738,8 @@ function registerGuardianCommands(program2) {
4762
6738
  SEP4();
4763
6739
  console.log(W4(` Address: ${G4(target)}`));
4764
6740
  console.log(W4(` Active guardian: ${active ? G4("yes") : DIM4("no")}`));
4765
- console.log(W4(` Own stake: ${BOLD4(formatUnits4(stake, 18))} WOOD`));
4766
- console.log(W4(` Delegated inbound: ${BOLD4(formatUnits4(delegatedIn, 18))} WOOD`));
6741
+ console.log(W4(` Own stake: ${BOLD4(formatUnits5(stake, 18))} WOOD`));
6742
+ console.log(W4(` Delegated inbound: ${BOLD4(formatUnits5(delegatedIn, 18))} WOOD`));
4767
6743
  console.log(W4(` Commission: ${BOLD4(`${Number(commission) / 100}%`)}`));
4768
6744
  SEP4();
4769
6745
  console.log();
@@ -4776,6 +6752,10 @@ function registerGuardianCommands(program2) {
4776
6752
  guardian.command("stake <amount>").description("Stake as a guardian. Amount in WOOD (min = registry.minGuardianStake).").option("--agent-id <id>", "ERC-8004 agent ID to associate (first-stake only)", "0").action(async (amountStr, opts) => {
4777
6753
  const amount = parseUnits5(amountStr, 18);
4778
6754
  const agentId = BigInt(opts.agentId);
6755
+ if (isCalldataOnly()) {
6756
+ emitCalldata(encodeGuardianStake({ amount, agentId: opts.agentId }, getChain().id));
6757
+ return;
6758
+ }
4779
6759
  await ensureWoodAllowance(amount);
4780
6760
  const wallet = getWalletClient();
4781
6761
  const pc = getPublicClient();
@@ -4783,7 +6763,7 @@ function registerGuardianCommands(program2) {
4783
6763
  try {
4784
6764
  const hash = await wallet.writeContract({
4785
6765
  address: registry(),
4786
- abi: GUARDIAN_REGISTRY_ABI,
6766
+ abi: GUARDIAN_REGISTRY_ABI2,
4787
6767
  functionName: "stakeAsGuardian",
4788
6768
  args: [amount, agentId],
4789
6769
  chain: null,
@@ -4803,11 +6783,15 @@ function registerGuardianCommands(program2) {
4803
6783
  console.error(chalk8.red(`Unknown action '${action}'. Use request | cancel | claim.`));
4804
6784
  process.exit(1);
4805
6785
  }
6786
+ if (isCalldataOnly()) {
6787
+ emitCalldata(encodeGuardianUnstake({ action }, getChain().id));
6788
+ return;
6789
+ }
4806
6790
  const wallet = getWalletClient();
4807
6791
  const pc = getPublicClient();
4808
6792
  const spinner = ora7(`${action}...`).start();
4809
6793
  try {
4810
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: fn, args: [], chain: null, account: wallet.account });
6794
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: fn, args: [], chain: null, account: wallet.account });
4811
6795
  await pc.waitForTransactionReceipt({ hash });
4812
6796
  spinner.succeed(`${action} confirmed. Tx: ${hash}`);
4813
6797
  } catch (err) {
@@ -4817,17 +6801,21 @@ function registerGuardianCommands(program2) {
4817
6801
  }
4818
6802
  });
4819
6803
  guardian.command("delegate <delegate> <amount>").description("Delegate WOOD stake to a guardian. Amount in WOOD.").action(async (delegate, amountStr) => {
4820
- if (!isAddress5(delegate)) {
6804
+ if (!isAddress6(delegate)) {
4821
6805
  console.error(chalk8.red("Invalid delegate address"));
4822
6806
  process.exit(1);
4823
6807
  }
4824
6808
  const amount = parseUnits5(amountStr, 18);
6809
+ if (isCalldataOnly()) {
6810
+ emitCalldata(encodeGuardianDelegate({ delegate, amount }, getChain().id));
6811
+ return;
6812
+ }
4825
6813
  await ensureWoodAllowance(amount);
4826
6814
  const wallet = getWalletClient();
4827
6815
  const pc = getPublicClient();
4828
6816
  const spinner = ora7(`Delegating ${amountStr} WOOD to ${delegate}...`).start();
4829
6817
  try {
4830
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "delegateStake", args: [delegate, amount], chain: null, account: wallet.account });
6818
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "delegateStake", args: [delegate, amount], chain: null, account: wallet.account });
4831
6819
  await pc.waitForTransactionReceipt({ hash });
4832
6820
  spinner.succeed(`Delegated. Tx: ${hash}`);
4833
6821
  } catch (err) {
@@ -4837,7 +6825,7 @@ function registerGuardianCommands(program2) {
4837
6825
  }
4838
6826
  });
4839
6827
  guardian.command("undelegate <delegate> <action>").description("Unstake-delegation flow: 'request' | 'cancel' | 'claim'").action(async (delegate, action) => {
4840
- if (!isAddress5(delegate)) {
6828
+ if (!isAddress6(delegate)) {
4841
6829
  console.error(chalk8.red("Invalid delegate address"));
4842
6830
  process.exit(1);
4843
6831
  }
@@ -4846,11 +6834,15 @@ function registerGuardianCommands(program2) {
4846
6834
  console.error(chalk8.red(`Unknown action '${action}'. Use request | cancel | claim.`));
4847
6835
  process.exit(1);
4848
6836
  }
6837
+ if (isCalldataOnly()) {
6838
+ emitCalldata(encodeDelegationUnstake({ action, delegate }, getChain().id));
6839
+ return;
6840
+ }
4849
6841
  const wallet = getWalletClient();
4850
6842
  const pc = getPublicClient();
4851
6843
  const spinner = ora7(`${action}...`).start();
4852
6844
  try {
4853
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: fn, args: [delegate], chain: null, account: wallet.account });
6845
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: fn, args: [delegate], chain: null, account: wallet.account });
4854
6846
  await pc.waitForTransactionReceipt({ hash });
4855
6847
  spinner.succeed(`${action} confirmed. Tx: ${hash}`);
4856
6848
  } catch (err) {
@@ -4865,11 +6857,15 @@ function registerGuardianCommands(program2) {
4865
6857
  console.error(chalk8.red("Commission cannot exceed 5000 bps (50%)"));
4866
6858
  process.exit(1);
4867
6859
  }
6860
+ if (isCalldataOnly()) {
6861
+ emitCalldata(encodeSetCommission({ bps: Number(bps) }, getChain().id));
6862
+ return;
6863
+ }
4868
6864
  const wallet = getWalletClient();
4869
6865
  const pc = getPublicClient();
4870
6866
  const spinner = ora7(`Setting commission to ${Number(bps) / 100}%...`).start();
4871
6867
  try {
4872
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "setCommission", args: [bps], chain: null, account: wallet.account });
6868
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "setCommission", args: [bps], chain: null, account: wallet.account });
4873
6869
  await pc.waitForTransactionReceipt({ hash });
4874
6870
  spinner.succeed(`Commission set. Tx: ${hash}`);
4875
6871
  } catch (err) {
@@ -4880,11 +6876,15 @@ function registerGuardianCommands(program2) {
4880
6876
  });
4881
6877
  guardian.command("claim-proposal <proposalId>").description("Claim guardian-fee commission for a settled proposal you voted Approve on (vault asset).").action(async (pidStr) => {
4882
6878
  const pid = BigInt(pidStr);
6879
+ if (isCalldataOnly()) {
6880
+ emitCalldata(encodeClaimProposalReward({ proposalId: pid }, getChain().id));
6881
+ return;
6882
+ }
4883
6883
  const wallet = getWalletClient();
4884
6884
  const pc = getPublicClient();
4885
6885
  const spinner = ora7("Claiming...").start();
4886
6886
  try {
4887
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "claimProposalReward", args: [pid], chain: null, account: wallet.account });
6887
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "claimProposalReward", args: [pid], chain: null, account: wallet.account });
4888
6888
  await pc.waitForTransactionReceipt({ hash });
4889
6889
  spinner.succeed(`Claimed. Tx: ${hash}`);
4890
6890
  } catch (err) {
@@ -4894,16 +6894,20 @@ function registerGuardianCommands(program2) {
4894
6894
  }
4895
6895
  });
4896
6896
  guardian.command("claim-delegator <delegate> <proposalId>").description("Claim your delegator share of a delegate's guardian-fee pool for a specific proposal.").action(async (delegate, pidStr) => {
4897
- if (!isAddress5(delegate)) {
6897
+ if (!isAddress6(delegate)) {
4898
6898
  console.error(chalk8.red("Invalid delegate address"));
4899
6899
  process.exit(1);
4900
6900
  }
4901
6901
  const pid = BigInt(pidStr);
6902
+ if (isCalldataOnly()) {
6903
+ emitCalldata(encodeClaimDelegatorProposalReward({ delegate, proposalId: pid }, getChain().id));
6904
+ return;
6905
+ }
4902
6906
  const wallet = getWalletClient();
4903
6907
  const pc = getPublicClient();
4904
6908
  const spinner = ora7("Claiming delegator share...").start();
4905
6909
  try {
4906
- const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI, functionName: "claimDelegatorProposalReward", args: [delegate, pid], chain: null, account: wallet.account });
6910
+ const hash = await wallet.writeContract({ address: registry(), abi: GUARDIAN_REGISTRY_ABI2, functionName: "claimDelegatorProposalReward", args: [delegate, pid], chain: null, account: wallet.account });
4907
6911
  await pc.waitForTransactionReceipt({ hash });
4908
6912
  spinner.succeed(`Claimed. Tx: ${hash}`);
4909
6913
  } catch (err) {
@@ -4933,6 +6937,7 @@ function registerGuardianCommands(program2) {
4933
6937
 
4934
6938
  // src/commands/grid.ts
4935
6939
  import chalk14 from "chalk";
6940
+ import { createInterface as createInterface2 } from "readline/promises";
4936
6941
 
4937
6942
  // src/grid/loop.ts
4938
6943
  import chalk13 from "chalk";
@@ -5183,7 +7188,7 @@ var GridManager = class {
5183
7188
  }
5184
7189
  this.portfolio.checkPauseThreshold(state, this.config, prices);
5185
7190
  await this.portfolio.save(state);
5186
- return { fills: totalFills, roundTrips: totalRoundTrips, pnlUsd: totalPnl, paused: false };
7191
+ return { fills: totalFills, roundTrips: totalRoundTrips, pnlUsd: totalPnl, paused: state.paused };
5187
7192
  }
5188
7193
  /** Hourly refresh of `grid.trend` from recent 4h candles. Called from
5189
7194
  * tick() so the downtrend filter sees fresh data between buildGrid runs. */
@@ -5472,7 +7477,8 @@ var DEFAULT_GRID_CONFIG = {
5472
7477
  // was 0.55 — rebalance faster, keep levels near price
5473
7478
  fullRebuildIntervalMs: 12 * 60 * 60 * 1e3,
5474
7479
  // 12h
5475
- tokenSplit: { bitcoin: 0.45, ethereum: 0.3, solana: 0.25 },
7480
+ tokenSplit: { bitcoin: 0.55, ethereum: 0.25, solana: 0.2 },
7481
+ // BTC-tilt — 30d backtest 2026-04-18..05-18 vs prior 0.45/0.30/0.25: net +$464 (+9.28%) vs +$422 (+8.44%) and max DD -$546 vs -$598
5476
7482
  minProfitPerFillUsd: 0.5,
5477
7483
  pauseThresholdPct: 0.2,
5478
7484
  // post leverage-fix calibration: fires at ~4% adverse move on 5x (real dollar terms); old 0.40 required ~8% which essentially never fired before liquidation
@@ -5550,8 +7556,14 @@ var GridHedgeManager = class {
5550
7556
  * @param marketTrend - recent market trend (e.g. BTC's 56h trend) used by
5551
7557
  * `hedgeRatioFor()` to scale hedge size by regime. Pass
5552
7558
  * undefined to use the static default.
7559
+ * @param paused - when true, force desired hedge to zero for all tokens so
7560
+ * the close-path realizes any unrealized PnL into the
7561
+ * ledger. The loop passes `state.paused` so a soft-pause
7562
+ * grid doesn't hold paper-gain on the hedge that erodes
7563
+ * when the underlying mean-reverts toward the resume
7564
+ * threshold.
5553
7565
  */
5554
- async tick(openFills, prices, now = Date.now(), marketTrend) {
7566
+ async tick(openFills, prices, now = Date.now(), marketTrend, paused = false) {
5555
7567
  const state = await this.load(now);
5556
7568
  let adjustments = 0;
5557
7569
  let unrealizedPnl = 0;
@@ -5559,7 +7571,7 @@ var GridHedgeManager = class {
5559
7571
  for (const fill of openFills) {
5560
7572
  const price = prices[fill.token];
5561
7573
  if (!price || price <= 0) continue;
5562
- const desiredShortQty = fill.fillCount >= MIN_FILLS_TO_HEDGE ? fill.totalQuantity * activeRatio : 0;
7574
+ const desiredShortQty = paused ? 0 : fill.fillCount >= MIN_FILLS_TO_HEDGE ? fill.totalQuantity * activeRatio : 0;
5563
7575
  const existing = state.positions.find((p) => p.token === fill.token);
5564
7576
  if (desiredShortQty <= 0) {
5565
7577
  if (existing && existing.quantity > 0) {
@@ -5651,6 +7663,39 @@ var GridHedgeManager = class {
5651
7663
  totalRealizedPnl: state.totalRealizedPnl
5652
7664
  };
5653
7665
  }
7666
+ /**
7667
+ * Close every active hedge position in the ledger at the provided mark
7668
+ * prices, accumulating realized PnL using the same `(entry - price) * qty`
7669
+ * formula `tick()` uses on its auto-close path. Positions whose token is
7670
+ * missing from `prices` are left untouched and reported in `skipped`.
7671
+ *
7672
+ * Bookkeeping-only — this manager does not place perp orders; the actual
7673
+ * HL position (if any) must be closed externally. Use this to zero the
7674
+ * ledger as part of `sherwood grid flatten`.
7675
+ */
7676
+ async flatten(prices, now = Date.now()) {
7677
+ const state = await this.load(now);
7678
+ const closed = [];
7679
+ const skipped = [];
7680
+ for (const pos of state.positions) {
7681
+ if (pos.quantity <= 0) continue;
7682
+ const price = prices[pos.token];
7683
+ if (!price || price <= 0) {
7684
+ skipped.push(pos.token);
7685
+ continue;
7686
+ }
7687
+ const closePnl = (pos.entryPrice - price) * pos.quantity;
7688
+ state.totalRealizedPnl += closePnl;
7689
+ state.todayRealizedPnl += closePnl;
7690
+ pos.realizedPnl += closePnl;
7691
+ closed.push({ token: pos.token, quantity: pos.quantity, realizedPnl: closePnl });
7692
+ pos.quantity = 0;
7693
+ pos.entryPrice = 0;
7694
+ pos.lastAdjustedAt = now;
7695
+ }
7696
+ await this.save();
7697
+ return { closed, skipped, totalRealizedPnl: state.totalRealizedPnl };
7698
+ }
5654
7699
  /** Get current hedge status for display. */
5655
7700
  getStatus() {
5656
7701
  if (!this.state) return null;
@@ -5716,13 +7761,13 @@ var GridExecutor = class {
5716
7761
  import chalk12 from "chalk";
5717
7762
  import { mkdir as mkdir3, readFile as readFile3, rename as rename2, writeFile as writeFile3 } from "fs/promises";
5718
7763
  import { dirname as dirname3 } from "path";
5719
- import { encodeAbiParameters as encodeAbiParameters11, parseUnits as parseUnits6 } from "viem";
7764
+ import { encodeAbiParameters as encodeAbiParameters13, parseUnits as parseUnits6 } from "viem";
5720
7765
 
5721
7766
  // src/grid/cloid.ts
5722
- import { keccak256, encodeAbiParameters as encodeAbiParameters10 } from "viem";
7767
+ import { keccak256 as keccak2562, encodeAbiParameters as encodeAbiParameters12 } from "viem";
5723
7768
  function gridCloid(strategy2, assetIndex, isBuy, levelIndex, nonce) {
5724
- const hash = keccak256(
5725
- encodeAbiParameters10(
7769
+ const hash = keccak2562(
7770
+ encodeAbiParameters12(
5726
7771
  [
5727
7772
  { type: "address" },
5728
7773
  { type: "uint32" },
@@ -5782,11 +7827,18 @@ var OnchainGridExecutor = class {
5782
7827
  this.maxOrdersPerTick = Number(value);
5783
7828
  return this.maxOrdersPerTick;
5784
7829
  }
5785
- /** Load persisted state. Call once before the first execute(). */
5786
- async load() {
7830
+ /**
7831
+ * Read persisted state from disk — does NOT touch the network. Use this
7832
+ * when you only need `nonces` + `placedCloids` (e.g. for `flattenAll`'s
7833
+ * cancel-only path). Idempotent: callers may invoke it repeatedly; each
7834
+ * call re-reads the file and clobbers in-memory maps with disk state.
7835
+ */
7836
+ async loadFromDisk() {
5787
7837
  try {
5788
7838
  const raw = await readFile3(this.statePath, "utf-8");
5789
7839
  const state = JSON.parse(raw);
7840
+ this.nonces.clear();
7841
+ this.placedCloids.clear();
5790
7842
  for (const [tok, n] of Object.entries(state.nonces)) this.nonces.set(tok, n);
5791
7843
  for (const [tok, cloids] of Object.entries(state.placedCloids)) {
5792
7844
  this.placedCloids.set(tok, cloids.map((s) => BigInt(s)));
@@ -5803,6 +7855,10 @@ var OnchainGridExecutor = class {
5803
7855
  );
5804
7856
  }
5805
7857
  }
7858
+ }
7859
+ /** Load persisted state + chain meta. Call once before the first execute(). */
7860
+ async load() {
7861
+ await this.loadFromDisk();
5806
7862
  if (!this.meta) this.meta = await hlGetMeta();
5807
7863
  await this.fetchMaxOrdersPerTick();
5808
7864
  }
@@ -5938,7 +7994,7 @@ var OnchainGridExecutor = class {
5938
7994
  for (const chunk of chunks) {
5939
7995
  const nonce = this.bumpNonce(token);
5940
7996
  const { encoded, cloids } = this.encodeOrders(assetIndex, chunk, meta, nonce);
5941
- const data = encodeAbiParameters11(
7997
+ const data = encodeAbiParameters13(
5942
7998
  [{ type: "uint8" }, { type: "tuple[]", components: [...GRID_ORDER_COMPONENTS] }],
5943
7999
  [ACTION_PLACE_GRID, encoded]
5944
8000
  );
@@ -5956,7 +8012,7 @@ var OnchainGridExecutor = class {
5956
8012
  this.bumpNonce(token);
5957
8013
  return "0x";
5958
8014
  }
5959
- const data = encodeAbiParameters11(
8015
+ const data = encodeAbiParameters13(
5960
8016
  [{ type: "uint8" }, { type: "uint32" }, { type: "uint128[]" }],
5961
8017
  [ACTION_CANCEL_ALL, assetIndex, cloids]
5962
8018
  );
@@ -5985,7 +8041,7 @@ var OnchainGridExecutor = class {
5985
8041
  const { encoded, cloids } = this.encodeOrders(assetIndex, chunk, meta, newNonce);
5986
8042
  let data;
5987
8043
  if (i === 0) {
5988
- data = encodeAbiParameters11(
8044
+ data = encodeAbiParameters13(
5989
8045
  [
5990
8046
  { type: "uint8" },
5991
8047
  { type: "uint32" },
@@ -5995,7 +8051,7 @@ var OnchainGridExecutor = class {
5995
8051
  [ACTION_CANCEL_AND_PLACE, assetIndex, oldCloids, encoded]
5996
8052
  );
5997
8053
  } else {
5998
- data = encodeAbiParameters11(
8054
+ data = encodeAbiParameters13(
5999
8055
  [{ type: "uint8" }, { type: "tuple[]", components: [...GRID_ORDER_COMPONENTS] }],
6000
8056
  [ACTION_PLACE_GRID, encoded]
6001
8057
  );
@@ -6008,6 +8064,47 @@ var OnchainGridExecutor = class {
6008
8064
  }
6009
8065
  return txs;
6010
8066
  }
8067
+ /**
8068
+ * Cancel every tracked CLOID across all configured tokens. Used by
8069
+ * `sherwood grid flatten` to wind the strategy down to a no-orders state
8070
+ * without placing anything new.
8071
+ *
8072
+ * Per-token errors are captured and returned rather than thrown — partial
8073
+ * progress is the expected outcome when one token reverts (e.g. nonce
8074
+ * desync after a manual on-chain action). Each successful cancel saves
8075
+ * before moving on, so a crash mid-flatten leaves a consistent on-disk
8076
+ * record of which tokens still hold cloids.
8077
+ */
8078
+ async flattenAll() {
8079
+ if (this.busy) {
8080
+ return { txs: [], cancelled: [], skipped: [], errors: ["flattenAll() called while another execute()/flattenAll() is in flight"] };
8081
+ }
8082
+ this.busy = true;
8083
+ try {
8084
+ await this.loadFromDisk();
8085
+ const txs = [];
8086
+ const cancelled = [];
8087
+ const skipped = [];
8088
+ const errors = [];
8089
+ for (const [token, assetIndex] of Object.entries(this.cfg.assetIndices)) {
8090
+ try {
8091
+ const tx = await this.cancelAll(token, assetIndex);
8092
+ if (tx === "0x") {
8093
+ skipped.push(token);
8094
+ } else {
8095
+ txs.push(tx);
8096
+ cancelled.push(token);
8097
+ }
8098
+ await this.save();
8099
+ } catch (e) {
8100
+ errors.push(`${token}: ${e.message}`);
8101
+ }
8102
+ }
8103
+ return { txs, cancelled, skipped, errors };
8104
+ } finally {
8105
+ this.busy = false;
8106
+ }
8107
+ }
6011
8108
  /**
6012
8109
  * Submit and wait for receipt. Throwing here surfaces to the per-token
6013
8110
  * try/catch in execute(), recording the error and skipping the save() so
@@ -6151,7 +8248,7 @@ var GridLoop = class {
6151
8248
  }
6152
8249
  const openExposure = this.manager.getOpenFillExposure();
6153
8250
  const marketTrend = this.manager.getMarketTrend();
6154
- const hedgeResult = await this.hedge.tick(openExposure, prices, Date.now(), marketTrend);
8251
+ const hedgeResult = await this.hedge.tick(openExposure, prices, Date.now(), marketTrend, result.paused);
6155
8252
  if (hedgeResult.adjustments > 0) {
6156
8253
  console.error(chalk13.magenta(
6157
8254
  ` [hedge] ${hedgeResult.adjustments} adjustment(s), unrealized: $${hedgeResult.unrealizedPnl.toFixed(2)}, total realized: $${hedgeResult.totalRealizedPnl.toFixed(2)}`
@@ -6598,7 +8695,7 @@ async function runBacktest(opts) {
6598
8695
  }
6599
8696
  const exposure = manager.getOpenFillExposure();
6600
8697
  const marketTrend = manager.getMarketTrend();
6601
- const hedgeResult = await hedge.tick(exposure, hedgePrices, t, marketTrend);
8698
+ const hedgeResult = await hedge.tick(exposure, hedgePrices, t, marketTrend, stateAfter?.paused ?? false);
6602
8699
  hedgeAdjustments += hedgeResult.adjustments;
6603
8700
  hedgeRealized = hedgeResult.totalRealizedPnl;
6604
8701
  hedgeUnrealized = hedgeResult.unrealizedPnl;
@@ -6930,6 +9027,9 @@ async function runSweep(opts) {
6930
9027
  }
6931
9028
 
6932
9029
  // src/commands/grid.ts
9030
+ var DEFAULT_PAUSE_REASON = "Manually paused by operator";
9031
+ var DEFAULT_FLATTEN_REASON = "Manually flattened by operator";
9032
+ var CONFIRM_TIMEOUT_SECONDS = 60;
6933
9033
  var DIM5 = chalk14.gray;
6934
9034
  var G5 = chalk14.green;
6935
9035
  var BOLD5 = chalk14.white.bold;
@@ -7022,6 +9122,21 @@ function printSweepSummary(s) {
7022
9122
  console.log(W5(` Saved: ${s.sweepId}/sweep.json + per-run JSONs in ~/.sherwood/grid/sweeps/`));
7023
9123
  console.log();
7024
9124
  }
9125
+ function parseAssetIndices(raw) {
9126
+ const indices = {};
9127
+ for (const pair of raw.split(",")) {
9128
+ const [tok, idx] = pair.split("=");
9129
+ if (!tok || !idx) {
9130
+ throw new Error(`Bad asset-indices pair: '${pair}' (expected token=index)`);
9131
+ }
9132
+ const n = Number(idx.trim());
9133
+ if (!Number.isInteger(n) || n < 0) {
9134
+ throw new Error(`Bad asset-indices value for '${tok}': '${idx}' (must be a non-negative integer)`);
9135
+ }
9136
+ indices[tok.trim()] = n;
9137
+ }
9138
+ return indices;
9139
+ }
7025
9140
  function parseTokenSplit(raw, tokens) {
7026
9141
  const split = {};
7027
9142
  for (const pair of raw.split(",")) {
@@ -7059,12 +9174,7 @@ function registerGridCommand(program2) {
7059
9174
  if (!opts.assetIndices) {
7060
9175
  throw new Error("--asset-indices required when --live (mainnet: --asset-indices bitcoin=0,ethereum=1,solana=5)");
7061
9176
  }
7062
- assetIndices = {};
7063
- for (const pair of opts.assetIndices.split(",")) {
7064
- const [tok, idx] = pair.split("=");
7065
- if (!tok || !idx) throw new Error(`Bad asset-indices pair: ${pair}`);
7066
- assetIndices[tok.trim()] = Number(idx);
7067
- }
9177
+ assetIndices = parseAssetIndices(opts.assetIndices);
7068
9178
  }
7069
9179
  const strategyAddress = opts.strategy;
7070
9180
  if (strategyAddress && !live) {
@@ -7227,7 +9337,7 @@ function registerGridCommand(program2) {
7227
9337
  });
7228
9338
  printSweepSummary(result);
7229
9339
  });
7230
- grid.command("pause").description("Manually pause the live grid (sets state.paused=true)").option("--reason <text>", "Reason for the pause (shown in status output)", "Manually paused by operator").option("--state-dir <path>", "Read/write state from a custom directory (defaults to ~/.sherwood/grid)").action(async (opts) => {
9340
+ grid.command("pause").description("Manually pause the live grid (sets state.paused=true)").option("--reason <text>", "Reason for the pause (shown in status output)", DEFAULT_PAUSE_REASON).option("--state-dir <path>", "Read/write state from a custom directory (defaults to ~/.sherwood/grid)").action(async (opts) => {
7231
9341
  const portfolio = new GridPortfolio(opts.stateDir);
7232
9342
  const state = await portfolio.load();
7233
9343
  if (!state) {
@@ -7273,6 +9383,116 @@ function registerGridCommand(program2) {
7273
9383
  console.log(G5(` Grid resumed. (was paused: ${wasReason || "<unknown>"})`));
7274
9384
  console.log();
7275
9385
  });
9386
+ grid.command("flatten").description("Cancel all open grid orders on-chain, zero the hedge ledger, and pause the grid").option("--state-dir <path>", "Read/write state from a custom directory (defaults to ~/.sherwood/grid)").option("--strategy <address>", "Strategy contract address \u2014 required to cancel on-chain CLOIDs. Omit for hedge-only flatten.").option("--asset-indices <pairs>", "comma-separated token=index pairs (required with --strategy, e.g. bitcoin=3,ethereum=4,solana=5)").option("--reason <text>", "Reason recorded on the paused grid state", DEFAULT_FLATTEN_REASON).option("--dry-run", "Print intended actions and exit without touching state or chain").option("--yes", "Skip the confirmation prompt").action(async (opts) => {
9387
+ const stateDir = opts.stateDir;
9388
+ const portfolio = new GridPortfolio(stateDir);
9389
+ const state = await portfolio.load();
9390
+ if (!state) {
9391
+ console.log(DIM5("\n No grid portfolio found. Nothing to flatten.\n"));
9392
+ return;
9393
+ }
9394
+ const openFills = state.grids.flatMap((g) => g.openFills.filter((f) => !f.closed));
9395
+ const hedgeMgr = new GridHedgeManager(stateDir);
9396
+ const hedgeState = await hedgeMgr.load();
9397
+ const openHedges = hedgeState.positions.filter((p) => p.quantity > 0);
9398
+ let strategyAddress;
9399
+ let assetIndices;
9400
+ if (opts.strategy) {
9401
+ strategyAddress = opts.strategy;
9402
+ if (!opts.assetIndices) {
9403
+ throw new Error("--asset-indices required with --strategy (e.g. --asset-indices bitcoin=3,ethereum=4,solana=5)");
9404
+ }
9405
+ assetIndices = parseAssetIndices(opts.assetIndices);
9406
+ }
9407
+ console.log();
9408
+ console.log(BOLD5(" Flatten plan"));
9409
+ SEP5();
9410
+ console.log(W5(` State dir: ${stateDir ?? "~/.sherwood/grid (default)"}`));
9411
+ console.log(W5(` On-chain: ${strategyAddress ? strategyAddress : DIM5("(skipped \u2014 no --strategy)")}`));
9412
+ console.log(W5(` Open fills: ${openFills.length} (informational \u2014 on-chain CLOIDs cancelled per-token)`));
9413
+ console.log(W5(` Hedge legs: ${openHedges.length}${openHedges.length > 0 ? " (" + openHedges.map((h) => h.token).join(", ") + ")" : ""}`));
9414
+ console.log(W5(` Will pause: yes \u2014 reason: "${opts.reason}"`));
9415
+ SEP5();
9416
+ if (opts.dryRun) {
9417
+ console.log(chalk14.yellow(" --dry-run set \u2014 no state or chain mutation."));
9418
+ console.log();
9419
+ return;
9420
+ }
9421
+ if (!opts.yes) {
9422
+ if (!process.stdin.isTTY) {
9423
+ throw new Error("Refusing to flatten without --yes in a non-interactive context. Pass --yes to confirm.");
9424
+ }
9425
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
9426
+ const ac = new AbortController();
9427
+ const timer = setTimeout(() => ac.abort(), CONFIRM_TIMEOUT_SECONDS * 1e3);
9428
+ let answer;
9429
+ try {
9430
+ answer = (await rl.question(chalk14.yellow(" Proceed with flatten? [y/N] "), { signal: ac.signal })).trim().toLowerCase();
9431
+ } catch (e) {
9432
+ if (e.name === "AbortError") {
9433
+ console.log(DIM5(`
9434
+ Timed out after ${CONFIRM_TIMEOUT_SECONDS}s. Aborted.
9435
+ `));
9436
+ return;
9437
+ }
9438
+ throw e;
9439
+ } finally {
9440
+ clearTimeout(timer);
9441
+ rl.close();
9442
+ }
9443
+ if (answer !== "y" && answer !== "yes") {
9444
+ console.log(DIM5(" Aborted.\n"));
9445
+ return;
9446
+ }
9447
+ }
9448
+ if (strategyAddress && assetIndices) {
9449
+ const executor = new OnchainGridExecutor({ strategyAddress, assetIndices, stateDir });
9450
+ await executor.load();
9451
+ const res = await executor.flattenAll();
9452
+ if (res.cancelled.length > 0) {
9453
+ console.log(G5(` Cancelled CLOIDs for: ${res.cancelled.join(", ")}`));
9454
+ for (const tx of res.txs) console.log(DIM5(` tx ${tx}`));
9455
+ }
9456
+ if (res.skipped.length > 0) {
9457
+ console.log(DIM5(` Skipped (no tracked CLOIDs): ${res.skipped.join(", ")}`));
9458
+ }
9459
+ if (res.errors.length > 0) {
9460
+ for (const err of res.errors) console.log(chalk14.red(` Cancel error: ${err}`));
9461
+ }
9462
+ }
9463
+ if (openHedges.length > 0) {
9464
+ const hl = new HyperliquidProvider();
9465
+ const prices = {};
9466
+ for (const pos of openHedges) {
9467
+ const data = await hl.getHyperliquidData(pos.token);
9468
+ if (data?.markPrice && data.markPrice > 0) prices[pos.token] = data.markPrice;
9469
+ }
9470
+ const hres = await hedgeMgr.flatten(prices);
9471
+ if (hres.closed.length > 0) {
9472
+ for (const c of hres.closed) {
9473
+ const sign = c.realizedPnl >= 0 ? "+" : "-";
9474
+ console.log(G5(` Hedge closed ${c.token}: qty ${c.quantity.toFixed(6)} \u2192 0 (${sign}$${Math.abs(c.realizedPnl).toFixed(2)} realized)`));
9475
+ }
9476
+ }
9477
+ if (hres.skipped.length > 0) {
9478
+ console.log(chalk14.yellow(` Hedge skipped (no mark price): ${hres.skipped.join(", ")}`));
9479
+ }
9480
+ console.log(W5(` Total hedge realized PnL: $${hres.totalRealizedPnl.toFixed(2)}`));
9481
+ } else {
9482
+ console.log(DIM5(" No active hedge legs \u2014 ledger already flat."));
9483
+ }
9484
+ if (!state.paused) {
9485
+ state.paused = true;
9486
+ state.pauseReason = opts.reason;
9487
+ await portfolio.save(state);
9488
+ console.log(chalk14.yellow(` Grid paused. Reason: ${state.pauseReason}`));
9489
+ } else {
9490
+ console.log(DIM5(` Grid was already paused: ${state.pauseReason}`));
9491
+ }
9492
+ console.log();
9493
+ console.log(G5(" Flatten complete. Stop the loop process / systemd unit if still running."));
9494
+ console.log();
9495
+ });
7276
9496
  }
7277
9497
 
7278
9498
  // src/index.ts
@@ -7281,7 +9501,7 @@ try {
7281
9501
  } catch {
7282
9502
  }
7283
9503
  var require2 = createRequire(import.meta.url);
7284
- var { version: CLI_VERSION } = require2("../package.json");
9504
+ var CLI_VERSION = "0.65.0";
7285
9505
  async function loadXmtp() {
7286
9506
  return import("./xmtp-MR6OJ76L.js");
7287
9507
  }
@@ -7295,7 +9515,7 @@ var BOLD6 = chalk15.white.bold;
7295
9515
  var LABEL5 = chalk15.green.bold;
7296
9516
  var SEP6 = () => console.log(DIM6("\u2500".repeat(60)));
7297
9517
  function validateAddress(value, name) {
7298
- if (!isAddress6(value)) {
9518
+ if (!isAddress7(value)) {
7299
9519
  console.error(chalk15.red(`Invalid ${name} address: ${value}`));
7300
9520
  process.exit(1);
7301
9521
  }
@@ -7309,8 +9529,13 @@ function resolveVault(opts) {
7309
9529
  var program = new Command();
7310
9530
  program.name("sherwood").description("CLI for agent-managed investment syndicates").version(CLI_VERSION).addOption(
7311
9531
  new Option("--chain <network>", "Target network").choices(VALID_NETWORKS).default("base")
7312
- ).option("--testnet", "Alias for --chain base-sepolia (deprecated)", false).hook("preAction", (thisCommand) => {
9532
+ ).option("--testnet", "Alias for --chain base-sepolia (deprecated)", false).option(
9533
+ "--calldata-only",
9534
+ "Print unsigned EIP-5792 calldata as JSON instead of signing/sending (for external signers \u2014 no private key required)",
9535
+ false
9536
+ ).hook("preAction", (thisCommand) => {
7313
9537
  const opts = thisCommand.optsWithGlobals();
9538
+ setCalldataOnly(Boolean(opts.calldataOnly));
7314
9539
  let network = opts.chain;
7315
9540
  if (opts.testnet) {
7316
9541
  process.env.ENABLE_TESTNET = "true";
@@ -7333,8 +9558,10 @@ syndicate.command("create").description("Create a new syndicate via the factory
7333
9558
  console.log();
7334
9559
  console.log(LABEL5(" \u25C6 Create Syndicate"));
7335
9560
  SEP6();
7336
- const wallet = getAccount();
7337
- console.log(DIM6(` Wallet: ${wallet.address}`));
9561
+ if (!isCalldataOnly()) {
9562
+ const wallet = getAccount();
9563
+ console.log(DIM6(` Wallet: ${wallet.address}`));
9564
+ }
7338
9565
  console.log(DIM6(` Network: ${getChain().name}`));
7339
9566
  SEP6();
7340
9567
  const savedAgentId = getAgentId();
@@ -7434,6 +9661,23 @@ syndicate.command("create").description("Create a new syndicate via the factory
7434
9661
  metadataURI = `data:application/json;base64,${Buffer.from(json).toString("base64")}`;
7435
9662
  }
7436
9663
  }
9664
+ if (isCalldataOnly()) {
9665
+ emitCalldata(
9666
+ encodeCreateSyndicate(
9667
+ {
9668
+ creatorAgentId: agentIdStr,
9669
+ metadataURI,
9670
+ asset,
9671
+ name,
9672
+ symbol,
9673
+ openDeposits,
9674
+ subdomain
9675
+ },
9676
+ getChain().id
9677
+ )
9678
+ );
9679
+ return;
9680
+ }
7437
9681
  const existingId = await subdomainExists(subdomain);
7438
9682
  if (existingId !== null) {
7439
9683
  const existingInfo = await getSyndicate(existingId);
@@ -7766,9 +10010,18 @@ syndicate.command("update-metadata").description("Update syndicate metadata (cre
7766
10010
  });
7767
10011
  syndicate.command("approve-depositor").description("Approve an address to deposit (owner only)").option("--vault <address>", "Vault address (default: from config)").requiredOption("--depositor <address>", "Address to approve").action(async (opts) => {
7768
10012
  resolveVault(opts);
10013
+ const depositor = validateAddress(opts.depositor, "depositor");
10014
+ if (isCalldataOnly()) {
10015
+ emitCalldata(
10016
+ encodeApproveDepositor(
10017
+ { vault: getVaultAddress(), depositor },
10018
+ getChain().id
10019
+ )
10020
+ );
10021
+ return;
10022
+ }
7769
10023
  const spinner = ora8("Approving depositor...").start();
7770
10024
  try {
7771
- const depositor = validateAddress(opts.depositor, "depositor");
7772
10025
  const hash = await approveDepositor(depositor);
7773
10026
  spinner.succeed(`Depositor approved: ${hash}`);
7774
10027
  console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
@@ -7793,6 +10046,29 @@ syndicate.command("remove-depositor").description("Remove an address from the de
7793
10046
  }
7794
10047
  });
7795
10048
  syndicate.command("add").description("Register an agent on a syndicate vault (creator only)").option("--vault <address>", "Vault address (default: from config)").option("--agent-id <id>", "Agent's ERC-8004 identity token ID (resolved from wallet if omitted)").requiredOption("--wallet <address>", "Agent wallet address").action(async (opts) => {
10049
+ if (isCalldataOnly()) {
10050
+ resolveVault(opts);
10051
+ const agentWallet = validateAddress(opts.wallet, "wallet");
10052
+ if (!opts.agentId) {
10053
+ console.error(
10054
+ chalk15.red(
10055
+ "--calldata-only requires --agent-id (auto-lookup from wallet is skipped in calldata mode)"
10056
+ )
10057
+ );
10058
+ process.exit(1);
10059
+ }
10060
+ emitCalldata(
10061
+ encodeRegisterAgent(
10062
+ {
10063
+ vault: getVaultAddress(),
10064
+ agentAddress: agentWallet,
10065
+ agentId: BigInt(opts.agentId)
10066
+ },
10067
+ getChain().id
10068
+ )
10069
+ );
10070
+ return;
10071
+ }
7796
10072
  const spinner = ora8("Verifying creator...").start();
7797
10073
  try {
7798
10074
  resolveVault(opts);
@@ -7941,6 +10217,24 @@ syndicate.command("join").description("Request to join a syndicate (creates an E
7941
10217
  }
7942
10218
  process.exit(1);
7943
10219
  }
10220
+ if (isCalldataOnly()) {
10221
+ const referrerAgentId2 = opts.ref ? parseInt(opts.ref, 10) : void 0;
10222
+ spinner.stop();
10223
+ emitCalldata(
10224
+ encodeJoinRequest(
10225
+ {
10226
+ syndicateId: syndicate2.id,
10227
+ agentId: BigInt(agentId),
10228
+ vault: syndicate2.vault,
10229
+ creator: syndicate2.creator,
10230
+ message: opts.message,
10231
+ referrerAgentId: referrerAgentId2
10232
+ },
10233
+ getChain().id
10234
+ )
10235
+ );
10236
+ return;
10237
+ }
7944
10238
  const callerAddress = getAccount().address;
7945
10239
  spinner.text = "Checking membership...";
7946
10240
  setVaultAddress(syndicate2.vault);
@@ -8116,6 +10410,21 @@ syndicate.command("approve").description("Approve an agent join request (registe
8116
10410
  const vaultAddress = getVaultAddress();
8117
10411
  const agentWallet = validateAddress(opts.wallet, "wallet");
8118
10412
  const { creator, subdomain, id: syndicateId } = await resolveVaultSyndicate(vaultAddress);
10413
+ if (isCalldataOnly()) {
10414
+ spinner.stop();
10415
+ emitCalldata(
10416
+ encodeApproveAgent(
10417
+ {
10418
+ syndicateId,
10419
+ agentId: BigInt(opts.agentId),
10420
+ vault: vaultAddress,
10421
+ agentAddress: agentWallet
10422
+ },
10423
+ getChain().id
10424
+ )
10425
+ );
10426
+ return;
10427
+ }
8119
10428
  const callerAddress = getAccount().address.toLowerCase();
8120
10429
  if (creator.toLowerCase() !== callerAddress) {
8121
10430
  spinner.fail("Only the syndicate creator can approve agents");
@@ -8297,10 +10606,57 @@ syndicate.command("set-primary").description("Set the active syndicate for CLI c
8297
10606
  }
8298
10607
  });
8299
10608
  var vaultCmd = program.command("vault");
8300
- vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").option("--use-eth", "Auto-wrap ETH \u2192 WETH before depositing (for WETH vaults)").action(async (opts) => {
10609
+ vaultCmd.command("deposit").description("Deposit into a vault").option("--vault <address>", "Vault address (default: from config)").requiredOption("--amount <amount>", "Amount to deposit (in asset units)").option("--use-eth", "Auto-wrap ETH \u2192 WETH before depositing (for WETH vaults)").option("--receiver <address>", "Depositor / share receiver (required with --calldata-only)").action(async (opts) => {
8301
10610
  resolveVault(opts);
8302
10611
  const decimals = await getAssetDecimals();
8303
10612
  const amount = parseUnits7(opts.amount, decimals);
10613
+ if (isCalldataOnly()) {
10614
+ if (!opts.receiver) {
10615
+ console.error(
10616
+ chalk15.red(
10617
+ "--calldata-only requires --receiver <address> (the depositor / share receiver)"
10618
+ )
10619
+ );
10620
+ process.exit(1);
10621
+ }
10622
+ const receiver = validateAddress(opts.receiver, "receiver");
10623
+ const asset = await getAssetAddress();
10624
+ const assetSymbol = await getPublicClient().readContract({
10625
+ address: asset,
10626
+ abi: ERC20_ABI,
10627
+ functionName: "symbol"
10628
+ });
10629
+ let currentWethBalance;
10630
+ if (opts.useEth) {
10631
+ currentWethBalance = await getPublicClient().readContract({
10632
+ address: asset,
10633
+ abi: ERC20_ABI,
10634
+ functionName: "balanceOf",
10635
+ args: [receiver]
10636
+ });
10637
+ }
10638
+ try {
10639
+ emitCalldata(
10640
+ encodeDeposit(
10641
+ {
10642
+ vault: getVaultAddress(),
10643
+ receiver,
10644
+ asset,
10645
+ assetSymbol,
10646
+ assetDecimals: decimals,
10647
+ assets: amount,
10648
+ wrapEth: Boolean(opts.useEth),
10649
+ currentWethBalance
10650
+ },
10651
+ getChain().id
10652
+ )
10653
+ );
10654
+ } catch (err) {
10655
+ console.error(chalk15.red(err instanceof Error ? err.message : String(err)));
10656
+ process.exit(1);
10657
+ }
10658
+ return;
10659
+ }
8304
10660
  try {
8305
10661
  if (!opts.useEth) {
8306
10662
  await preflightDeposit(amount);
@@ -8368,10 +10724,42 @@ vaultCmd.command("balance").description("Show LP share balance and asset value")
8368
10724
  process.exit(1);
8369
10725
  }
8370
10726
  });
8371
- vaultCmd.command("redeem").description("Redeem vault shares for the underlying asset (ERC-4626)").option("--vault <address>", "Vault address (default: from config)").option("--shares <amount>", "Shares to redeem in whole-share units (default: all)").option("--receiver <address>", "Receiver of the underlying asset (default: your wallet)").action(async (opts) => {
10727
+ vaultCmd.command("redeem").description("Redeem vault shares for the underlying asset (ERC-4626)").option("--vault <address>", "Vault address (default: from config)").option("--shares <amount>", "Shares to redeem in whole-share units (default: all)").option("--receiver <address>", "Receiver of the underlying asset (default: your wallet)").option("--owner <address>", "Share owner whose shares burn (required with --calldata-only)").action(async (opts) => {
8372
10728
  resolveVault(opts);
8373
10729
  const assetDecimals = await getAssetDecimals();
8374
10730
  const shareDecimals = assetDecimals * 2;
10731
+ if (isCalldataOnly()) {
10732
+ if (!opts.shares) {
10733
+ console.error(
10734
+ chalk15.red(
10735
+ "--calldata-only requires --shares (auto-'all' needs your on-chain share balance)"
10736
+ )
10737
+ );
10738
+ process.exit(1);
10739
+ }
10740
+ if (!opts.owner) {
10741
+ console.error(
10742
+ chalk15.red(
10743
+ "--calldata-only requires --owner <address> (the share holder whose shares burn)"
10744
+ )
10745
+ );
10746
+ process.exit(1);
10747
+ }
10748
+ const owner = validateAddress(opts.owner, "owner");
10749
+ const receiver = opts.receiver ? validateAddress(opts.receiver, "receiver") : owner;
10750
+ emitCalldata(
10751
+ encodeRedeem(
10752
+ {
10753
+ vault: getVaultAddress(),
10754
+ receiver,
10755
+ owner,
10756
+ shares: parseUnits7(opts.shares, shareDecimals)
10757
+ },
10758
+ getChain().id
10759
+ )
10760
+ );
10761
+ return;
10762
+ }
8375
10763
  let shares;
8376
10764
  try {
8377
10765
  if (opts.shares) {
@@ -8389,7 +10777,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
8389
10777
  `));
8390
10778
  process.exit(1);
8391
10779
  }
8392
- if (opts.receiver && !isAddress6(opts.receiver)) {
10780
+ if (opts.receiver && !isAddress7(opts.receiver)) {
8393
10781
  console.error(chalk15.red(`
8394
10782
  \u2716 Invalid receiver address: ${opts.receiver}
8395
10783
  `));
@@ -8403,7 +10791,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
8403
10791
  `));
8404
10792
  process.exit(1);
8405
10793
  }
8406
- const sharesDisplay = formatUnits5(shares, shareDecimals);
10794
+ const sharesDisplay = formatUnits6(shares, shareDecimals);
8407
10795
  const spinner = ora8(`Redeeming ${sharesDisplay} shares...`).start();
8408
10796
  try {
8409
10797
  const { hash, assetsOut } = await redeem(
@@ -8412,7 +10800,7 @@ vaultCmd.command("redeem").description("Redeem vault shares for the underlying a
8412
10800
  );
8413
10801
  spinner.succeed(`Redeemed: ${hash}`);
8414
10802
  console.log(chalk15.dim(` ${getExplorerUrl(hash)}`));
8415
- console.log(` Assets received: ${formatUnits5(assetsOut, assetDecimals)}`);
10803
+ console.log(` Assets received: ${formatUnits6(assetsOut, assetDecimals)}`);
8416
10804
  } catch (err) {
8417
10805
  spinner.fail("Redeem failed");
8418
10806
  console.error(chalk15.red(formatContractError(err)));