@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 +2867 -479
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
2166
|
+
return encodeAbiParameters6(INIT_PARAMS_TYPES3, [params]);
|
|
477
2167
|
}
|
|
478
2168
|
function buildExecuteCalls4(clone, weth, supplyAmount) {
|
|
479
|
-
const approveAmount = supplyAmount === 0n ?
|
|
2169
|
+
const approveAmount = supplyAmount === 0n ? maxUint2562 : supplyAmount;
|
|
480
2170
|
return [
|
|
481
2171
|
{
|
|
482
2172
|
target: weth,
|
|
483
|
-
data:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
2213
|
+
import { encodeAbiParameters as encodeAbiParameters7, encodeFunctionData as encodeFunctionData12 } from "viem";
|
|
524
2214
|
function buildInitData5(underlying, mamoFactory, minRedeemAmount) {
|
|
525
|
-
return
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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 ?
|
|
2340
|
+
const approveAmount = amount === 0n ? maxUint2563 : amount;
|
|
651
2341
|
return [
|
|
652
2342
|
{
|
|
653
2343
|
target: asset,
|
|
654
|
-
data:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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 ?
|
|
2399
|
+
const approveAmount = amount === 0n ? maxUint2564 : amount;
|
|
710
2400
|
return [
|
|
711
2401
|
{
|
|
712
2402
|
target: asset,
|
|
713
|
-
data:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
2462
|
+
const encoded2 = encodeAbiParameters11([{ type: "bytes" }], [path]);
|
|
773
2463
|
return `0x01${encoded2.slice(2)}`;
|
|
774
2464
|
}
|
|
775
|
-
const encoded =
|
|
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 (
|
|
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 (
|
|
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
|
|
1387
|
-
if (
|
|
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
|
|
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 (!
|
|
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 (!
|
|
3160
|
+
if (!isAddress2(clone)) {
|
|
1471
3161
|
console.error(chalk.red("Invalid clone address"));
|
|
1472
3162
|
process.exit(1);
|
|
1473
3163
|
}
|
|
1474
|
-
if (!
|
|
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 (!
|
|
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 ${
|
|
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 (!
|
|
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(
|
|
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(
|
|
1835
|
-
const investedNum = Number(
|
|
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:
|
|
3666
|
+
balance: formatUnits2(balance, decimals),
|
|
1849
3667
|
balanceRaw: balance.toString(),
|
|
1850
|
-
investedAmount:
|
|
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(
|
|
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:
|
|
3704
|
+
totalDeployed: formatUnits2(totalAmount, assetDecimals),
|
|
1887
3705
|
maxSlippageBps: Number(maxSlippage),
|
|
1888
|
-
assetBalance:
|
|
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: ${
|
|
3731
|
+
console.log(` Deployed: ${formatUnits2(totalAmount, assetDecimals)} ${assetSymbol}`);
|
|
1914
3732
|
console.log(` Max Slip: ${Number(maxSlippage) / 100}%`);
|
|
1915
|
-
console.log(` Asset held: ${
|
|
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: ${
|
|
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 (!
|
|
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: ${
|
|
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 =
|
|
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 =
|
|
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: ${
|
|
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
|
|
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: ${
|
|
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 ${
|
|
4091
|
+
balSpinner.fail(`Insufficient VVV: have ${formatUnits3(vvvBalance, 18)}, need ${opts.amount}`);
|
|
2274
4092
|
process.exit(1);
|
|
2275
4093
|
}
|
|
2276
|
-
balSpinner.succeed(`VVV balance: ${
|
|
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 ${
|
|
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 ${
|
|
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: ${
|
|
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: ${
|
|
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: ${
|
|
2398
|
-
console.log(chalk2.red(` Approx additional sVVV needed: ${
|
|
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 ${
|
|
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 (!
|
|
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: ${
|
|
2521
|
-
console.log(` VVV (unstaked): ${
|
|
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}: ${
|
|
4344
|
+
console.log(` ${label}: ${formatUnits3(balance, 18)} sVVV`);
|
|
2527
4345
|
}
|
|
2528
4346
|
console.log(chalk2.bold("\n Your Wallet"));
|
|
2529
|
-
console.log(` sVVV: ${
|
|
2530
|
-
console.log(` Pending rewards: ${
|
|
2531
|
-
console.log(` DIEM (credits): ${
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 (!
|
|
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: ${
|
|
2758
|
-
console.log(` Deposited: ${
|
|
2759
|
-
console.log(` Profit: ${
|
|
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 (${
|
|
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: ${
|
|
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: ${
|
|
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 (!
|
|
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: ${
|
|
2880
|
-
console.log(` Deposited: ${
|
|
2881
|
-
console.log(` Profit: ${
|
|
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}: ${
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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(
|
|
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}: ${
|
|
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
|
|
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 ${
|
|
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 =
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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 =
|
|
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 || !
|
|
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
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
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
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
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
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
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
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
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
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
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
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
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
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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 (!
|
|
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:
|
|
4750
|
-
pc.readContract({ address: registry(), abi:
|
|
4751
|
-
pc.readContract({ address: registry(), abi:
|
|
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:
|
|
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(
|
|
4766
|
-
console.log(W4(` Delegated inbound: ${BOLD4(
|
|
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:
|
|
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:
|
|
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 (!
|
|
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:
|
|
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 (!
|
|
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:
|
|
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:
|
|
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:
|
|
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 (!
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
|
7764
|
+
import { encodeAbiParameters as encodeAbiParameters13, parseUnits as parseUnits6 } from "viem";
|
|
5720
7765
|
|
|
5721
7766
|
// src/grid/cloid.ts
|
|
5722
|
-
import { keccak256, encodeAbiParameters as
|
|
7767
|
+
import { keccak256 as keccak2562, encodeAbiParameters as encodeAbiParameters12 } from "viem";
|
|
5723
7768
|
function gridCloid(strategy2, assetIndex, isBuy, levelIndex, nonce) {
|
|
5724
|
-
const hash =
|
|
5725
|
-
|
|
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
|
-
/**
|
|
5786
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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)",
|
|
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
|
|
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 (!
|
|
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).
|
|
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
|
-
|
|
7337
|
-
|
|
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 && !
|
|
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 =
|
|
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: ${
|
|
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)));
|