@candledottv/cli 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +1547 -282
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -524,7 +524,7 @@ var init_terminal_theme = __esm(() => {
524
524
  });
525
525
 
526
526
  // src/version.ts
527
- var CLI_VERSION = "0.13.0";
527
+ var CLI_VERSION = "0.13.1";
528
528
 
529
529
  // src/presentation.ts
530
530
  function terminalStyle(deps, stream, machine = false) {
@@ -38089,7 +38089,7 @@ var init_convert = __esm(() => {
38089
38089
  });
38090
38090
 
38091
38091
  // ../mcp/src/version.ts
38092
- var SERVER_VERSION = "0.10.22";
38092
+ var SERVER_VERSION = "0.10.23";
38093
38093
 
38094
38094
  // ../mcp/src/update-notice.ts
38095
38095
  function newer(a, b) {
@@ -38382,6 +38382,909 @@ var init_image = __esm(() => {
38382
38382
  nodeFs = { realpath, lstat, open: open4 };
38383
38383
  });
38384
38384
 
38385
+ // ../mcp/src/launch-definition.ts
38386
+ var LAUNCH_DEFINITION2;
38387
+ var init_launch_definition = __esm(() => {
38388
+ LAUNCH_DEFINITION2 = {
38389
+ version: 1,
38390
+ wallet: {
38391
+ embeddedNames: [
38392
+ "embedded",
38393
+ "main"
38394
+ ],
38395
+ help: "The paying wallet: a TEE wallet name, or embedded (main) for the account's embedded wallet."
38396
+ },
38397
+ quoteDecimals: {
38398
+ sol: 9,
38399
+ usdc: 6,
38400
+ cndl: 6,
38401
+ eth: 18,
38402
+ usdg: 6
38403
+ },
38404
+ quoteChains: {
38405
+ sol: "solana",
38406
+ usdc: "solana",
38407
+ cndl: "solana",
38408
+ eth: "hood",
38409
+ usdg: "hood"
38410
+ },
38411
+ defaultQuote: {
38412
+ solana: "sol",
38413
+ hood: "eth"
38414
+ },
38415
+ buyLimits: {
38416
+ defaults: {
38417
+ sol: {
38418
+ env: "DEV_BUY_MAX_LAMPORTS",
38419
+ raw: "500000000",
38420
+ unit: "lamports"
38421
+ },
38422
+ usdc: {
38423
+ env: "DEV_BUY_MAX_USDC_RAW",
38424
+ raw: "500000000",
38425
+ unit: "USDC raw units"
38426
+ },
38427
+ cndl: {
38428
+ env: "DEV_BUY_MAX_CNDL_RAW",
38429
+ raw: "2000000000000",
38430
+ unit: "CNDL raw units"
38431
+ },
38432
+ eth: {
38433
+ env: "DEV_BUY_MAX_WEI",
38434
+ raw: "20000000000000000",
38435
+ unit: "wei"
38436
+ }
38437
+ },
38438
+ thresholdBps: 1000,
38439
+ presetThresholds: {
38440
+ "exp-a": {
38441
+ sol: "5000000000",
38442
+ cndl: "330000000000"
38443
+ },
38444
+ "exp-b": {
38445
+ sol: "5000000000",
38446
+ cndl: "330000000000"
38447
+ },
38448
+ "exp-c": {
38449
+ sol: "5000000000",
38450
+ cndl: "330000000000"
38451
+ }
38452
+ }
38453
+ },
38454
+ surfacesIntro: [
38455
+ "CLI: scripts and terminals. Agents always pass --json.",
38456
+ "MCP: chat agents (Claude, the connector). The same launch fields and results as the CLI's --json.",
38457
+ "SDK: code that embeds Candle."
38458
+ ],
38459
+ fields: [
38460
+ {
38461
+ key: "wallet",
38462
+ type: "string",
38463
+ help: "The paying wallet: a TEE wallet name, or embedded (main) for the account's embedded wallet.",
38464
+ required: true,
38465
+ surfaces: {
38466
+ cli: {
38467
+ flag: "--wallet",
38468
+ value: "<tee>|embedded"
38469
+ },
38470
+ mcp: {
38471
+ key: "wallet"
38472
+ },
38473
+ sdk: {
38474
+ key: "wallet"
38475
+ }
38476
+ }
38477
+ },
38478
+ {
38479
+ key: "clientLaunchId",
38480
+ type: "string",
38481
+ help: "An idempotency key of at most 128 characters; reuse it to recover the same launch.",
38482
+ range: {
38483
+ min: "1",
38484
+ max: "128",
38485
+ unit: "characters"
38486
+ },
38487
+ surfaces: {
38488
+ cli: {
38489
+ flag: "--client-trade-id",
38490
+ value: "<id>"
38491
+ },
38492
+ mcp: {
38493
+ key: "clientLaunchId"
38494
+ },
38495
+ sdk: {
38496
+ key: "clientLaunchId"
38497
+ }
38498
+ }
38499
+ },
38500
+ {
38501
+ key: "name",
38502
+ type: "string",
38503
+ help: "The token's name, at most 32 UTF-8 bytes.",
38504
+ required: true,
38505
+ range: {
38506
+ min: "1",
38507
+ max: "32",
38508
+ unit: "UTF-8 bytes"
38509
+ },
38510
+ surfaces: {
38511
+ cli: {
38512
+ flag: "--name",
38513
+ value: "<name>"
38514
+ },
38515
+ mcp: {
38516
+ key: "name"
38517
+ },
38518
+ sdk: {
38519
+ key: "name"
38520
+ }
38521
+ }
38522
+ },
38523
+ {
38524
+ key: "symbol",
38525
+ type: "string",
38526
+ help: "The token's ticker, at most 10 UTF-8 bytes.",
38527
+ required: true,
38528
+ range: {
38529
+ min: "1",
38530
+ max: "10",
38531
+ unit: "UTF-8 bytes"
38532
+ },
38533
+ surfaces: {
38534
+ cli: {
38535
+ flag: "--symbol",
38536
+ value: "<symbol>"
38537
+ },
38538
+ mcp: {
38539
+ key: "symbol"
38540
+ },
38541
+ sdk: {
38542
+ key: "symbol"
38543
+ }
38544
+ }
38545
+ },
38546
+ {
38547
+ key: "imageUrl",
38548
+ type: "url",
38549
+ help: "The roughly square token image at an https URL; --image <path> uploads a local file instead.",
38550
+ required: true,
38551
+ surfaces: {
38552
+ cli: {
38553
+ flag: "--image-url",
38554
+ value: "<https-url>"
38555
+ },
38556
+ mcp: {
38557
+ key: "imageUrl"
38558
+ },
38559
+ sdk: {
38560
+ key: "imageUrl"
38561
+ }
38562
+ }
38563
+ },
38564
+ {
38565
+ key: "bannerUrl",
38566
+ type: "url",
38567
+ help: "A wide banner at an https URL or a local file; uploads are public and permanent.",
38568
+ surfaces: {
38569
+ cli: {
38570
+ flag: "--banner",
38571
+ value: "<file|URL>"
38572
+ },
38573
+ mcp: {
38574
+ key: "bannerUrl"
38575
+ },
38576
+ sdk: {
38577
+ key: "bannerUrl"
38578
+ }
38579
+ }
38580
+ },
38581
+ {
38582
+ key: "description",
38583
+ type: "string",
38584
+ help: "The token's description, safe for on-chain JSON.",
38585
+ surfaces: {
38586
+ cli: {
38587
+ flag: "--description",
38588
+ value: "<text>"
38589
+ },
38590
+ mcp: {
38591
+ key: "description"
38592
+ },
38593
+ sdk: {
38594
+ key: "description"
38595
+ }
38596
+ }
38597
+ },
38598
+ {
38599
+ key: "socials.website",
38600
+ type: "url",
38601
+ help: "The token's website link as an absolute http(s) URL.",
38602
+ surfaces: {
38603
+ cli: {
38604
+ flag: "--website",
38605
+ value: "<url>"
38606
+ },
38607
+ mcp: {
38608
+ key: "socials.website"
38609
+ },
38610
+ sdk: {
38611
+ key: "socials.website"
38612
+ }
38613
+ }
38614
+ },
38615
+ {
38616
+ key: "socials.twitter",
38617
+ type: "url",
38618
+ help: "The token's X link as an absolute http(s) URL.",
38619
+ surfaces: {
38620
+ cli: {
38621
+ flag: "--x",
38622
+ value: "<url>"
38623
+ },
38624
+ mcp: {
38625
+ key: "socials.twitter"
38626
+ },
38627
+ sdk: {
38628
+ key: "socials.twitter"
38629
+ }
38630
+ }
38631
+ },
38632
+ {
38633
+ key: "socials.telegram",
38634
+ type: "url",
38635
+ help: "The token's telegram link as an absolute http(s) URL.",
38636
+ surfaces: {
38637
+ cli: {
38638
+ flag: "--telegram",
38639
+ value: "<url>"
38640
+ },
38641
+ mcp: {
38642
+ key: "socials.telegram"
38643
+ },
38644
+ sdk: {
38645
+ key: "socials.telegram"
38646
+ }
38647
+ }
38648
+ },
38649
+ {
38650
+ key: "socials.discord",
38651
+ type: "url",
38652
+ help: "The token's discord link as an absolute http(s) URL.",
38653
+ surfaces: {
38654
+ cli: {
38655
+ flag: "--discord",
38656
+ value: "<url>"
38657
+ },
38658
+ mcp: {
38659
+ key: "socials.discord"
38660
+ },
38661
+ sdk: {
38662
+ key: "socials.discord"
38663
+ }
38664
+ }
38665
+ },
38666
+ {
38667
+ key: "chain",
38668
+ type: "enum",
38669
+ values: [
38670
+ "solana",
38671
+ "hood"
38672
+ ],
38673
+ default: "solana",
38674
+ help: "The launch chain, inferred from the wallet, quote asset and dex version on the CLI.",
38675
+ surfaces: {
38676
+ cli: {
38677
+ derived: "wallet, --quote-asset and --dex-version"
38678
+ },
38679
+ mcp: {
38680
+ key: "chain"
38681
+ },
38682
+ sdk: {
38683
+ key: "chain"
38684
+ }
38685
+ }
38686
+ },
38687
+ {
38688
+ key: "quoteAsset",
38689
+ type: "enum",
38690
+ help: "The quote asset: sol, usdc or cndl on Solana; eth or usdg on Hood (defaults sol and eth).",
38691
+ values: [
38692
+ "sol",
38693
+ "usdc",
38694
+ "cndl",
38695
+ "eth",
38696
+ "usdg"
38697
+ ],
38698
+ surfaces: {
38699
+ cli: {
38700
+ flag: "--quote-asset",
38701
+ value: "sol|usdc|cndl|eth|usdg"
38702
+ },
38703
+ mcp: {
38704
+ key: "quoteAsset"
38705
+ },
38706
+ sdk: {
38707
+ key: "quoteAsset"
38708
+ }
38709
+ }
38710
+ },
38711
+ {
38712
+ key: "mode",
38713
+ type: "enum",
38714
+ help: "The curve mode: open or exclusive; test-open or test-exclusive need ENABLE_TEST_CURVES; exp-a, exp-b, exp-c and exp-custom are Solana sol/cndl only.",
38715
+ values: [
38716
+ "open",
38717
+ "exclusive",
38718
+ "test-open",
38719
+ "test-exclusive",
38720
+ "exp-a",
38721
+ "exp-b",
38722
+ "exp-c",
38723
+ "exp-custom"
38724
+ ],
38725
+ default: "open",
38726
+ surfaces: {
38727
+ cli: {
38728
+ flag: "--mode",
38729
+ value: "<mode>"
38730
+ },
38731
+ mcp: {
38732
+ key: "mode"
38733
+ },
38734
+ sdk: {
38735
+ key: "mode"
38736
+ }
38737
+ }
38738
+ },
38739
+ {
38740
+ key: "curve.virtualQuote",
38741
+ type: "decimal",
38742
+ help: "exp-custom's virtual quote, in whole quote units: 1 to 100 SOL or 66000 to 6600000 CNDL; requires --curve-g and EXP_CUSTOM_CURVES.",
38743
+ modes: [
38744
+ "exp-custom"
38745
+ ],
38746
+ chains: [
38747
+ "solana"
38748
+ ],
38749
+ requires: [
38750
+ "curve.threshold"
38751
+ ],
38752
+ range: {
38753
+ unit: "whole quote units",
38754
+ byQuote: {
38755
+ sol: {
38756
+ min: "1",
38757
+ max: "100"
38758
+ },
38759
+ cndl: {
38760
+ min: "66000",
38761
+ max: "6600000"
38762
+ }
38763
+ }
38764
+ },
38765
+ surfaces: {
38766
+ cli: {
38767
+ flag: "--curve-v",
38768
+ value: "<V>"
38769
+ },
38770
+ mcp: {
38771
+ key: "curve.virtualQuote"
38772
+ },
38773
+ sdk: {
38774
+ key: "curve.virtualQuote"
38775
+ }
38776
+ }
38777
+ },
38778
+ {
38779
+ key: "curve.threshold",
38780
+ type: "decimal",
38781
+ help: "exp-custom's graduation threshold, in whole quote units: 1 to 250 SOL or 66000 to 16500000 CNDL; requires --curve-v and EXP_CUSTOM_CURVES.",
38782
+ modes: [
38783
+ "exp-custom"
38784
+ ],
38785
+ chains: [
38786
+ "solana"
38787
+ ],
38788
+ requires: [
38789
+ "curve.virtualQuote"
38790
+ ],
38791
+ range: {
38792
+ unit: "whole quote units",
38793
+ byQuote: {
38794
+ sol: {
38795
+ min: "1",
38796
+ max: "250"
38797
+ },
38798
+ cndl: {
38799
+ min: "66000",
38800
+ max: "16500000"
38801
+ }
38802
+ }
38803
+ },
38804
+ surfaces: {
38805
+ cli: {
38806
+ flag: "--curve-g",
38807
+ value: "<G>"
38808
+ },
38809
+ mcp: {
38810
+ key: "curve.threshold"
38811
+ },
38812
+ sdk: {
38813
+ key: "curve.threshold"
38814
+ }
38815
+ }
38816
+ },
38817
+ {
38818
+ key: "creatorFeeBps",
38819
+ type: "enum",
38820
+ help: "An exp- mode's extra creator fee: 0, 100 or 200 bps on the curve and pool, paid to the creator in full.",
38821
+ values: [
38822
+ 0,
38823
+ 100,
38824
+ 200
38825
+ ],
38826
+ default: 0,
38827
+ surfaces: {
38828
+ cli: {
38829
+ flag: "--creator-fee-bps",
38830
+ value: "0|100|200"
38831
+ },
38832
+ mcp: {
38833
+ key: "creatorFeeBps"
38834
+ },
38835
+ sdk: {
38836
+ key: "creatorFeeBps"
38837
+ }
38838
+ }
38839
+ },
38840
+ {
38841
+ key: "stakerAllocationBps",
38842
+ type: "integer",
38843
+ help: "The staker allocation, 50 to 1000 bps; defaults to 50, or 500 on exp- curves.",
38844
+ range: {
38845
+ min: "50",
38846
+ max: "1000",
38847
+ unit: "bps"
38848
+ },
38849
+ default: 50,
38850
+ surfaces: {
38851
+ cli: {
38852
+ flag: "--staker-allocation-bps",
38853
+ value: "<bps>"
38854
+ },
38855
+ mcp: {
38856
+ key: "stakerAllocationBps"
38857
+ },
38858
+ sdk: {
38859
+ key: "stakerAllocationBps"
38860
+ }
38861
+ }
38862
+ },
38863
+ {
38864
+ key: "poolFee.tier",
38865
+ type: "enum",
38866
+ help: "Solana's graduated pool fee: 25, 50 or 100 bps (default 50), or the ending rate with --pool-fee-from.",
38867
+ values: [
38868
+ 25,
38869
+ 50,
38870
+ 100
38871
+ ],
38872
+ default: 50,
38873
+ chains: [
38874
+ "solana"
38875
+ ],
38876
+ surfaces: {
38877
+ cli: {
38878
+ flag: "--pool-fee",
38879
+ value: "25|50|100"
38880
+ },
38881
+ mcp: {
38882
+ key: "poolFee.tier"
38883
+ },
38884
+ sdk: {
38885
+ key: "poolFee.tier"
38886
+ }
38887
+ }
38888
+ },
38889
+ {
38890
+ key: "poolFee.startBps",
38891
+ type: "integer",
38892
+ help: "The falling pool fee's starting rate, at most 2500 bps; requires --pool-fee and --pool-fee-minutes.",
38893
+ range: {
38894
+ min: "25",
38895
+ max: "2500",
38896
+ unit: "bps"
38897
+ },
38898
+ chains: [
38899
+ "solana"
38900
+ ],
38901
+ requires: [
38902
+ "poolFee.tier",
38903
+ "poolFee.minutes"
38904
+ ],
38905
+ surfaces: {
38906
+ cli: {
38907
+ flag: "--pool-fee-from",
38908
+ value: "<bps>"
38909
+ },
38910
+ mcp: {
38911
+ key: "poolFee.startBps"
38912
+ },
38913
+ sdk: {
38914
+ key: "poolFee.startBps"
38915
+ }
38916
+ }
38917
+ },
38918
+ {
38919
+ key: "poolFee.minutes",
38920
+ type: "decimal",
38921
+ help: "The pool fee's fall time, at most 30 minutes; requires --pool-fee and --pool-fee-from.",
38922
+ wireType: "number",
38923
+ range: {
38924
+ min: "0.006666666666666667",
38925
+ max: "30",
38926
+ unit: "minutes"
38927
+ },
38928
+ chains: [
38929
+ "solana"
38930
+ ],
38931
+ requires: [
38932
+ "poolFee.tier",
38933
+ "poolFee.startBps"
38934
+ ],
38935
+ surfaces: {
38936
+ cli: {
38937
+ flag: "--pool-fee-minutes",
38938
+ value: "<min>"
38939
+ },
38940
+ mcp: {
38941
+ key: "poolFee.minutes"
38942
+ },
38943
+ sdk: {
38944
+ key: "poolFee.minutes"
38945
+ }
38946
+ }
38947
+ },
38948
+ {
38949
+ key: "poolFee.endBps",
38950
+ type: "enum",
38951
+ values: [
38952
+ 25,
38953
+ 50,
38954
+ 100
38955
+ ],
38956
+ chains: [
38957
+ "solana"
38958
+ ],
38959
+ help: "The falling fee's ending rate, taken from --pool-fee.",
38960
+ surfaces: {
38961
+ cli: {
38962
+ derived: "--pool-fee with --pool-fee-from"
38963
+ },
38964
+ mcp: {
38965
+ key: "poolFee.endBps"
38966
+ },
38967
+ sdk: {
38968
+ key: "poolFee.endBps"
38969
+ }
38970
+ }
38971
+ },
38972
+ {
38973
+ key: "poolOpen",
38974
+ type: "enum",
38975
+ help: "Solana preset pools: curve_price opens at the curve's last price and burns the rest; omitted uses curve_price when allocations fit graduation.maxStakerCarveBps, otherwise legacy; explicit curve_price above that limit is refused.",
38976
+ values: [
38977
+ "legacy",
38978
+ "curve_price"
38979
+ ],
38980
+ chains: [
38981
+ "solana"
38982
+ ],
38983
+ surfaces: {
38984
+ cli: {
38985
+ flag: "--pool-open",
38986
+ value: "legacy|curve_price"
38987
+ },
38988
+ mcp: {
38989
+ key: "poolOpen"
38990
+ },
38991
+ sdk: {
38992
+ key: "poolOpen"
38993
+ }
38994
+ }
38995
+ },
38996
+ {
38997
+ key: "expPoolShape",
38998
+ type: "enum",
38999
+ help: "Solana exp- pools: floor by default, or full_range to open across the whole price range.",
39000
+ values: [
39001
+ "floor",
39002
+ "full_range"
39003
+ ],
39004
+ default: "floor",
39005
+ chains: [
39006
+ "solana"
39007
+ ],
39008
+ surfaces: {
39009
+ cli: {
39010
+ flag: "--exp-pool-shape",
39011
+ value: "floor|full_range"
39012
+ },
39013
+ mcp: {
39014
+ key: "expPoolShape"
39015
+ },
39016
+ sdk: {
39017
+ key: "expPoolShape"
39018
+ }
39019
+ }
39020
+ },
39021
+ {
39022
+ key: "dexVersion",
39023
+ type: "enum",
39024
+ help: "Hood only, required there: the Uniswap version the curve graduates to.",
39025
+ values: [
39026
+ "v3",
39027
+ "v4"
39028
+ ],
39029
+ chains: [
39030
+ "hood"
39031
+ ],
39032
+ surfaces: {
39033
+ cli: {
39034
+ flag: "--dex-version",
39035
+ value: "v3|v4"
39036
+ },
39037
+ mcp: {
39038
+ key: "dexVersion"
39039
+ },
39040
+ sdk: {
39041
+ key: "dexVersion"
39042
+ }
39043
+ }
39044
+ },
39045
+ {
39046
+ key: "visibility",
39047
+ type: "enum",
39048
+ help: "The token's visibility: production, test, local or hidden; test keys require test or hidden.",
39049
+ values: [
39050
+ "production",
39051
+ "test",
39052
+ "local",
39053
+ "hidden"
39054
+ ],
39055
+ default: "production",
39056
+ surfaces: {
39057
+ cli: {
39058
+ flag: "--visibility",
39059
+ value: "production|test|local|hidden"
39060
+ },
39061
+ mcp: {
39062
+ key: "visibility"
39063
+ },
39064
+ sdk: {
39065
+ key: "visibility"
39066
+ }
39067
+ }
39068
+ },
39069
+ {
39070
+ key: "buy",
39071
+ type: "decimal",
39072
+ help: "An optional first buy in whole quote units; zero or empty means no buy, and the server applies dev-buy ceilings and TEE_LAUNCH_BUYS_ENABLED for TEE buys.",
39073
+ default: "0",
39074
+ range: {
39075
+ min: "0",
39076
+ unit: "whole quote units"
39077
+ },
39078
+ more: "Default raw caps (apps/api/src/lib/headless-validation.ts): DEV_BUY_MAX_LAMPORTS=500000000, DEV_BUY_MAX_USDC_RAW=500000000, DEV_BUY_MAX_CNDL_RAW=2000000000000, DEV_BUY_MAX_WEI=20000000000000000. On exp- curves the lower of the quote cap and 10% of G applies.",
39079
+ surfaces: {
39080
+ cli: {
39081
+ flag: "--buy",
39082
+ value: "<amount>"
39083
+ },
39084
+ mcp: {
39085
+ key: "buy"
39086
+ },
39087
+ sdk: {
39088
+ key: "buy"
39089
+ }
39090
+ }
39091
+ },
39092
+ {
39093
+ key: "dryRun",
39094
+ type: "boolean",
39095
+ help: "Print the launch preview without building, signing or recording a launch; local image uploads still become public and permanent.",
39096
+ default: false,
39097
+ surfaces: {
39098
+ cli: {
39099
+ flag: "--dry-run"
39100
+ },
39101
+ mcp: {
39102
+ key: "dryRun"
39103
+ },
39104
+ sdk: {
39105
+ key: "dryRun"
39106
+ }
39107
+ }
39108
+ }
39109
+ ]
39110
+ };
39111
+ });
39112
+
39113
+ // ../mcp/src/launch-schema.ts
39114
+ function mcpKey(field) {
39115
+ const surface = field.surfaces.mcp;
39116
+ return "key" in surface ? surface.key : undefined;
39117
+ }
39118
+ function topKey(key) {
39119
+ return key.split(".")[0] ?? key;
39120
+ }
39121
+ function mcpLaunchFields() {
39122
+ return LAUNCH_DEFINITION2.fields.filter((field) => {
39123
+ const key = mcpKey(field);
39124
+ return key !== undefined && !M2_TOP_KEYS.has(topKey(key));
39125
+ });
39126
+ }
39127
+ function fieldByKey(key) {
39128
+ const field = LAUNCH_DEFINITION2.fields.find((item) => item.key === key);
39129
+ if (!field)
39130
+ throw new Error(`Missing launch field ${key}`);
39131
+ return field;
39132
+ }
39133
+ function launchFieldHelp(key) {
39134
+ return fieldByKey(key).help;
39135
+ }
39136
+ function stringValues(key) {
39137
+ const field = fieldByKey(key);
39138
+ const values = "values" in field ? field.values : undefined;
39139
+ if (!values || values.some((value) => typeof value !== "string")) {
39140
+ throw new Error(`Expected string values for ${key}`);
39141
+ }
39142
+ return [...values];
39143
+ }
39144
+ function numberValues(key) {
39145
+ const field = fieldByKey(key);
39146
+ const values = "values" in field ? field.values : undefined;
39147
+ if (!values || values.length < 2 || values.some((value) => typeof value !== "number")) {
39148
+ throw new Error(`Expected number values for ${key}`);
39149
+ }
39150
+ return [...values];
39151
+ }
39152
+ function rangeMax(key) {
39153
+ const field = fieldByKey(key);
39154
+ if (!("range" in field) || field.range === undefined || !("max" in field.range)) {
39155
+ throw new Error(`Missing max for ${key}`);
39156
+ }
39157
+ return Number(field.range.max);
39158
+ }
39159
+ function numberEnum(key) {
39160
+ const [first, second, ...rest] = numberValues(key);
39161
+ return exports_external.union([exports_external.literal(first), exports_external.literal(second), ...rest.map((value) => exports_external.literal(value))]);
39162
+ }
39163
+ function described(schema, help, required) {
39164
+ return required ? schema.describe(help) : schema.optional().describe(help);
39165
+ }
39166
+ function isRequired(field) {
39167
+ return "required" in field && field.required === true || field.key === "clientLaunchId";
39168
+ }
39169
+ function launchSchemaField(shape, key) {
39170
+ const field = shape[key];
39171
+ if (!field)
39172
+ throw new Error(`Missing launch schema field ${key}`);
39173
+ return field;
39174
+ }
39175
+ function socialsRefusal(socials) {
39176
+ if (socials === undefined)
39177
+ return;
39178
+ if (!socials || typeof socials !== "object" || Array.isArray(socials)) {
39179
+ return { code: "VALIDATION_FAILED", message: SOCIALS_ONLY, field: "socials" };
39180
+ }
39181
+ for (const [key, value] of Object.entries(socials)) {
39182
+ if (!SOCIAL_KEYS.includes(key))
39183
+ return { code: "VALIDATION_FAILED", message: SOCIALS_ONLY, field: "socials" };
39184
+ if (value === undefined)
39185
+ continue;
39186
+ try {
39187
+ if (typeof value !== "string")
39188
+ throw new Error("not a string");
39189
+ const url = new URL(value);
39190
+ if (!["http:", "https:"].includes(url.protocol))
39191
+ throw new Error("not http(s)");
39192
+ } catch {
39193
+ return { code: "VALIDATION_FAILED", message: `${key} must be an http(s) URL.`, field: `socials.${key}` };
39194
+ }
39195
+ }
39196
+ return;
39197
+ }
39198
+ function socialsSchema() {
39199
+ const shape = {};
39200
+ const helps = [];
39201
+ for (const field of mcpLaunchFields()) {
39202
+ const key = mcpKey(field);
39203
+ if (!key?.startsWith("socials."))
39204
+ continue;
39205
+ shape[key.slice("socials.".length)] = exports_external.string().optional().describe(field.help);
39206
+ helps.push(field.help);
39207
+ }
39208
+ return exports_external.object(shape).passthrough().optional().describe(helps.join(" "));
39209
+ }
39210
+ function curveSchema() {
39211
+ const virtual = launchFieldHelp("curve.virtualQuote");
39212
+ const threshold = launchFieldHelp("curve.threshold");
39213
+ return exports_external.object({
39214
+ virtualQuote: exports_external.string().describe(virtual),
39215
+ threshold: exports_external.string().describe(threshold)
39216
+ }).strict().optional().describe(`${virtual} ${threshold}`);
39217
+ }
39218
+ function poolFeeSchema() {
39219
+ const tierHelp = launchFieldHelp("poolFee.tier");
39220
+ const startHelp = launchFieldHelp("poolFee.startBps");
39221
+ const endHelp = launchFieldHelp("poolFee.endBps");
39222
+ const minutesHelp = launchFieldHelp("poolFee.minutes");
39223
+ return exports_external.union([
39224
+ exports_external.object({ tier: numberEnum("poolFee.tier").describe(tierHelp) }).strict(),
39225
+ exports_external.object({
39226
+ startBps: exports_external.number().int().max(rangeMax("poolFee.startBps")).describe(startHelp),
39227
+ endBps: numberEnum("poolFee.endBps").describe(endHelp),
39228
+ minutes: exports_external.number().positive().max(rangeMax("poolFee.minutes")).describe(minutesHelp)
39229
+ }).strict()
39230
+ ]).optional().describe([tierHelp, startHelp, endHelp, minutesHelp].join(" "));
39231
+ }
39232
+ function scalarSchema(field) {
39233
+ const values = "values" in field ? field.values : undefined;
39234
+ let schema;
39235
+ if (values?.every((value) => typeof value === "number"))
39236
+ schema = numberEnum(field.key);
39237
+ else if (values?.every((value) => typeof value === "string"))
39238
+ schema = exports_external.enum(stringValues(field.key));
39239
+ else if (field.type === "boolean")
39240
+ schema = exports_external.boolean();
39241
+ else if (field.type === "integer" || "wireType" in field && field.wireType === "number")
39242
+ schema = exports_external.number();
39243
+ else
39244
+ schema = exports_external.string();
39245
+ return described(schema, field.help, isRequired(field));
39246
+ }
39247
+ function buildLaunchTokenShape() {
39248
+ const shape = {};
39249
+ for (const field of mcpLaunchFields()) {
39250
+ const key = mcpKey(field);
39251
+ if (!key)
39252
+ continue;
39253
+ const top = topKey(key);
39254
+ if (GROUPED_TOP_KEYS.has(top) || top in shape)
39255
+ continue;
39256
+ shape[top] = scalarSchema(field);
39257
+ }
39258
+ shape.socials = socialsSchema();
39259
+ shape.curve = curveSchema();
39260
+ shape.poolFee = poolFeeSchema();
39261
+ shape.buyAmount = exports_external.union([exports_external.string(), exports_external.number()]).optional().describe(BUY_AMOUNT_DESCRIPTION);
39262
+ return shape;
39263
+ }
39264
+ var M2_TOP_KEYS, GROUPED_TOP_KEYS, LAUNCH_SHARED_SENTENCES, SOCIAL_KEYS, SOCIALS_ONLY, BUY_AMOUNT_DESCRIPTION = "Initial buy, in quote base units (string or number)", DEV_BUY_DESCRIPTION, launchTokenShape, launchAndSeedShape;
39265
+ var init_launch_schema = __esm(() => {
39266
+ init_zod();
39267
+ init_launch_definition();
39268
+ M2_TOP_KEYS = new Set(["wallet", "buy"]);
39269
+ GROUPED_TOP_KEYS = new Set(["socials", "curve", "poolFee"]);
39270
+ LAUNCH_SHARED_SENTENCES = mcpLaunchFields().map((field) => field.help).join(" ");
39271
+ SOCIAL_KEYS = mcpLaunchFields().flatMap((field) => {
39272
+ const key = mcpKey(field);
39273
+ return key?.startsWith("socials.") ? [key.slice("socials.".length)] : [];
39274
+ });
39275
+ SOCIALS_ONLY = `socials accepts only ${SOCIAL_KEYS.join(", ")}.`;
39276
+ DEV_BUY_DESCRIPTION = 'Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), paid from the account's embedded wallet; the tool's description says how " + "each chain sends it. Follows quoteAsset, which defaults to sol on solana and eth on hood. " + "Capped by the platform dev-buy ceiling; for a larger seed, launch then follow with candle_trade.";
39277
+ launchTokenShape = buildLaunchTokenShape();
39278
+ launchAndSeedShape = (() => {
39279
+ const { buyAmount: _rawBuyAmount, ...seedable } = launchTokenShape;
39280
+ return {
39281
+ ...seedable,
39282
+ clientLaunchId: exports_external.string().optional().describe(`${launchFieldHelp("clientLaunchId")} Auto-generated when omitted and echoed in the result.`),
39283
+ devBuy: exports_external.string().optional().describe(DEV_BUY_DESCRIPTION)
39284
+ };
39285
+ })();
39286
+ });
39287
+
38385
39288
  // ../mcp/src/market-disclosure.ts
38386
39289
  var EXTERNAL_MARKET_DISCLOSURE;
38387
39290
  var init_market_disclosure = __esm(() => {
@@ -38641,6 +39544,9 @@ async function executeTrade(args, cfg, doFetch) {
38641
39544
  async function executeLaunchAndSeed(args, cfg, doFetch, imageDir, imageFs, opts = {}) {
38642
39545
  const apiKey = requireApiKey(cfg);
38643
39546
  const clientLaunchId = args.clientLaunchId ?? randomUUID3();
39547
+ const socialsProblem = socialsRefusal(args.socials);
39548
+ if (socialsProblem)
39549
+ return { text: imageRefusalBody(socialsProblem, { clientLaunchId }), isError: true };
38644
39550
  const { devBuy, dryRun, buyAmount: _rawBuyAmount, ...imageFields } = args;
38645
39551
  let buyAmount;
38646
39552
  if (devBuy !== undefined) {
@@ -38909,6 +39815,7 @@ var SOLANA_SEED_CEILINGS, MAIN_PAYER, SWEEP_BASE_ASSETS;
38909
39815
  var init_orchestrate = __esm(() => {
38910
39816
  init_convert();
38911
39817
  init_image();
39818
+ init_launch_schema();
38912
39819
  init_update_notice();
38913
39820
  SOLANA_SEED_CEILINGS = {
38914
39821
  sol: { raw: 500000000n, unit: "lamports", expRaw: 500000000n },
@@ -42705,8 +43612,7 @@ __export(exports_tools, {
42705
43612
  STAKING_DISCLOSURE: () => STAKING_DISCLOSURE,
42706
43613
  PROFILE_PNL_HYPERLIQUID: () => PROFILE_PNL_HYPERLIQUID,
42707
43614
  PROFILE_EDIT_TOOL_NAMES: () => PROFILE_EDIT_TOOL_NAMES,
42708
- OPERATION_NOT_FOUND_RULE: () => OPERATION_NOT_FOUND_RULE,
42709
- CUSTOM_CURVE_RULE: () => CUSTOM_CURVE_RULE
43615
+ OPERATION_NOT_FOUND_RULE: () => OPERATION_NOT_FOUND_RULE
42710
43616
  });
42711
43617
  import { randomUUID as randomUUID4 } from "node:crypto";
42712
43618
  function resolveToolAllowlist(env) {
@@ -43092,9 +43998,13 @@ function registerToolSubset(server, options) {
43092
43998
  const imageDir = configuredImageDir(env);
43093
43999
  register("candle_launch_token", {
43094
44000
  title: "Launch a token on Candle",
43095
- description: "Launch a new token via the Candle headless launch API, from the account's embedded wallet. Works on every plan, Free included. Set dryRun: true to validate without spending anything. " + CUSTOM_CURVE_RULE + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
44001
+ description: "Launch a new token via the Candle headless launch API, from the account's embedded wallet. Works on every plan, Free included. Set dryRun: true to validate without spending anything. " + LAUNCH_SHARED_SENTENCES + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
43096
44002
  inputSchema: imageDir ? localLaunchTokenShape : launchTokenShape
43097
44003
  }, async (args) => {
44004
+ const socialsProblem = socialsRefusal("socials" in args ? args.socials : undefined);
44005
+ if (socialsProblem) {
44006
+ return { content: [{ type: "text", text: imageRefusalBody(socialsProblem) }], isError: true };
44007
+ }
43098
44008
  const image = await resolveLaunchImage(args, imageDir, getConfig(), doFetch);
43099
44009
  if ("refused" in image) {
43100
44010
  return { content: [{ type: "text", text: imageRefusalBody(image.refused) }], isError: true };
@@ -43352,7 +44262,7 @@ MARKET_NOT_FOUND means Candle has no market for that token and this could not ru
43352
44262
  });
43353
44263
  register("candle_launch_and_seed", {
43354
44264
  title: "Launch a token and seed it",
43355
- description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed, then " + "return the fresh market state and token links in one result. On solana a seed makes this " + (options.headlessSeed === true ? "the seed is a separate first-buy transaction in the same Jito bundle as the launch, so " + "both land or neither does. That works on every plan, Free included, and needs no " + "swap:write. On hood the seed is " : "the atomic launch: the launch and the seed are two transactions Candle signs and sends as " + "one Jito bundle, so both land or neither does. That needs Pro or Max (TIER_REQUIRED " + "otherwise) and swap:write on the key, and the result carries bundleId. On hood the seed is ") + "a best-effort follow-up. A launch with no seed works on every plan, Free included. MOVES " + "REAL FUNDS unless dryRun; dry-run first, which spends nothing. Seeds above the platform " + "dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH): at most 0.5 SOL / 500 USDC / 2,000,000 " + "CNDL, or on an exp- curve 10% of the threshold when that is lower; the server holds the " + "creator's own seed to the effective ceiling, so launch with the largest devBuy it allows " + "and top up with candle_trade. " + CUSTOM_CURVE_RULE + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
44265
+ description: "Launch a new token from the account's embedded wallet, with an optional dev-buy seed, then " + "return the fresh market state and token links in one result. On solana a seed makes this " + (options.headlessSeed === true ? "the seed is a separate first-buy transaction in the same Jito bundle as the launch, so " + "both land or neither does. That works on every plan, Free included, and needs no " + "swap:write. On hood the seed is " : "the atomic launch: the launch and the seed are two transactions Candle signs and sends as " + "one Jito bundle, so both land or neither does. That needs Pro or Max (TIER_REQUIRED " + "otherwise) and swap:write on the key, and the result carries bundleId. On hood the seed is ") + "a best-effort follow-up. A launch with no seed works on every plan, Free included. MOVES " + "REAL FUNDS unless dryRun; dry-run first, which spends nothing. Seeds above the platform " + "dev-buy ceiling are rejected (DEV_BUY_TOO_HIGH): at most 0.5 SOL / 500 USDC / 2,000,000 " + "CNDL, or on an exp- curve 10% of the threshold when that is lower; the server holds the " + "creator's own seed to the effective ceiling, so launch with the largest devBuy it allows " + "and top up with candle_trade. " + LAUNCH_SHARED_SENTENCES + (imageDir ? IMAGE_PATH_DISCLOSURE : ""),
43356
44266
  inputSchema: imageDir ? localLaunchAndSeedShape : launchAndSeedShape
43357
44267
  }, async (args) => {
43358
44268
  const result = await executeLaunchAndSeed(args, getConfig(), doFetch, imageDir, undefined, {
@@ -43512,12 +44422,13 @@ async function registerEligibleProfileTool(server, env = process.env, doFetch =
43512
44422
  }
43513
44423
  return eligible;
43514
44424
  }
43515
- var VAMP_CANDIDATES_DESCRIPTION, CUSTOM_CURVE_RULE, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, launchTokenShape, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getOperationShape, vampCandidatesShape, getFeesShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, _rawBuyAmount, seedableLaunchShape, seedShape, launchAndSeedShape, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
44425
+ var VAMP_CANDIDATES_DESCRIPTION, localProfileTools, STAKING_DISCLOSURE = "No instant unstake. Read pool.withdrawalTimelockSeconds for the wait. Solana rewards accrue to the holding wallet. sCNDL in any eligible account wallet counts toward Pro and earns the account's Hood staking reward, except a wallet another account also claims, wallets past the 200-wallet cap, and an account with no EVM wallet. CNDL in a pending withdrawal counts toward nothing for Pro.", stakingWriteShape, TOOL_NAMES, PROFILE_EDIT_TOOL_NAMES, isProfileEditTool = (name) => PROFILE_EDIT_TOOL_NAMES.includes(name), OPERATION_NOT_FOUND_RULE, getMarketShape, tokenForensicsShape, FEED_LIMIT_MESSAGE = "limit must be a whole number from 1 to 200", feedLimit, getFeedShape, reportActivityShape, getAgentProfileShape, listAgentsShape, createPostShape, deletePostShape, listAgentPostsShape, getOperationShape, vampCandidatesShape, getFeesShape, claimFeesShape, resolveTokenShape, profileWalletsShape, profilePnlShape, profileTradesShape, setProfileWalletsShape, swapShape, tradeShape, tokenImageUrl, tokenBannerUrl, localImageShape, localLaunchTokenShape, localLaunchAndSeedShape, IMAGE_PATH_DISCLOSURE, transferShape, sweepShape, PROFILE_PNL_HYPERLIQUID;
43516
44426
  var init_tools = __esm(() => {
43517
44427
  init_zod();
43518
44428
  init_client2();
43519
44429
  init_convert();
43520
44430
  init_image();
44431
+ init_launch_schema();
43521
44432
  init_market_disclosure();
43522
44433
  init_orchestrate();
43523
44434
  init_perps();
@@ -43526,7 +44437,6 @@ var init_tools = __esm(() => {
43526
44437
  init_plans();
43527
44438
  init_update_notice();
43528
44439
  VAMP_CANDIDATES_DESCRIPTION = "Lists Solana tokens that are close to graduating or graduated in the last 6 hours whose ticker other tokens " + "are copying, most-copied in the last 24 hours first. Each entry carries a launch draft (name, ticker, image URL) " + "for `candle_launch_token` or `candle_launch_and_seed`; draft notes identify fields you must supply or adjust. " + "Upload your own image through the agent image upload when the image is unavailable. Use `dryRun: true` first. " + "Counts exclude the candidate itself and cover both Solana and Hood launches at the venues returned in " + "`coverage`, not Candle's own. `coverage.since` is the returned Solana coverage date; Hood history may differ.";
43529
- CUSTOM_CURVE_RULE = 'mode "exp-custom" with curve { virtualQuote, threshold } launches a Solana curve with your own V (the virtual ' + "quote it starts with) and G (the quote it raises before graduating), in whole SOL or CNDL as decimal strings, " + "accepted when V is 1 to 100 SOL and G 1 to 250 SOL (66,000 to 6,600,000 and 66,000 to 16,500,000 CNDL), when " + "every price up to 20 x G stays under the ceiling (V + 20G)^2 < V x X at this stakerAllocationBps, when neither " + "is finer than the quote's decimals, and when the dev buy is at most 0.5 SOL (2,000,000 CNDL), or 10% of G when " + "that is lower (every exp- mode; an operator may set the 0.5 SOL lower); a curve past a limit is " + "CURVE_OUT_OF_RANGE, and at the ceiling it carries maxThresholdRaw, the largest G that fits. " + "Dry-run it first (dryRun: true): the dry run spends nothing and returns curveTerms, the market cap at start and " + "at graduation, the supply sold by graduation, the share your own buyAmount takes, the fees and the migration " + "terms, and devBuyMax, the dev-buy ceiling that applies. Only where the API has EXP_CUSTOM_CURVES on; " + "elsewhere exp-custom is refused as an unknown mode.";
43530
44440
  localProfileTools = new WeakMap;
43531
44441
  stakingWriteShape = {
43532
44442
  payer: exports_external.enum(["main", "linked"]).optional().describe("main is the embedded wallet; linked names a linked or TEE wallet"),
@@ -43597,38 +44507,6 @@ var init_tools = __esm(() => {
43597
44507
  "candle_delete_post"
43598
44508
  ];
43599
44509
  OPERATION_NOT_FOUND_RULE = "No record exists under this id in the ledger `kind` asked. That does not prove the write did " + "not run: a write that is still starting has no record yet. If a write was sent with this id, " + "send the SAME request again with the SAME id. It returns the stored outcome, or reports the one " + "still running. It never executes a second time. A refusal of that resend (a conflict or a " + "limit) is an answer about the resend, not about the write: read candle_get_operation again " + "before reporting an outcome. Never send it under a new id: a new id is a second trade or " + "launch. Check `kind`: a swap id is not read here, so replay the swap with its `clientSwapId`.";
43600
- launchTokenShape = {
43601
- clientLaunchId: exports_external.string().describe("Caller-chosen idempotency key, unique per account"),
43602
- name: exports_external.string().describe("Token name"),
43603
- symbol: exports_external.string().describe("Token symbol"),
43604
- imageUrl: exports_external.string().describe("https URL to the token image. Must be roughly SQUARE (aspect ratio at most 1.5:1): it " + "renders as a small circle/square avatar everywhere. Share cards, OG images and banners " + "are rejected with IMAGE_WRONG_SHAPE -- pass those as bannerUrl instead"),
43605
- bannerUrl: exports_external.string().optional().describe("Optional https URL to WIDE artwork for the token page's banner strip (wider than 1.5:1, " + "e.g. 1200x630). This is where a share card or OG image belongs. A square image here is " + "rejected with BANNER_WRONG_SHAPE: send the wide original, not a cropped copy. Omit it and the strip " + "falls back to imageUrl"),
43606
- chain: exports_external.string().optional().describe('"solana" or "hood"; defaults to solana'),
43607
- quoteAsset: exports_external.string().optional().describe("Quote asset symbol; defaults per chain"),
43608
- mode: exports_external.string().optional().describe('"open" or "exclusive"; defaults to open. "test-open" / "test-exclusive" launch the ' + "low-threshold test curves (~1/80 economics) where the API has ENABLE_TEST_CURVES on. " + '"exp-a", "exp-b" and "exp-c" are the experimental fast Solana curves (sol or cndl). ' + '"exp-custom" launches a Solana curve with your own V and G, given in curve, where the API has it ' + "switched on"),
43609
- curve: exports_external.object({
43610
- virtualQuote: exports_external.string().describe('V: the virtual quote the curve starts with, in whole quote units, e.g. "12" SOL'),
43611
- threshold: exports_external.string().describe('G: the quote the curve raises before it graduates, in whole quote units, e.g. "40" SOL')
43612
- }).strict().optional().describe('mode "exp-custom" only, and required there. Decimal strings in whole units of the quote asset (sol or ' + "cndl), never raw units. Dry-run first: the API checks the limits and returns the curve's terms"),
43613
- creatorFeeBps: exports_external.union([exports_external.literal(0), exports_external.literal(100), exports_external.literal(200)]).optional().describe("An exp- mode only: an extra creator fee of 100 or 200 basis points on top of the curve's 1% and the " + "pool's 0.5%, paid to the creator in full. 0 is the same as leaving it out"),
43614
- stakerAllocationBps: exports_external.number().optional().describe("Staker allocation in basis points"),
43615
- poolFee: exports_external.union([
43616
- exports_external.object({ tier: exports_external.union([exports_external.literal(25), exports_external.literal(50), exports_external.literal(100)]) }).strict(),
43617
- exports_external.object({
43618
- startBps: exports_external.number().int().max(2500),
43619
- endBps: exports_external.union([exports_external.literal(25), exports_external.literal(50), exports_external.literal(100)]),
43620
- minutes: exports_external.number().positive().max(30)
43621
- }).strict()
43622
- ]).optional().describe("Solana only: the graduated pool's fee. { tier: 25 | 50 | 100 } is a flat fee in bps (default 50), or " + "{ startBps, endBps, minutes } is an anti-snipe fall from startBps (at most 2500, at least endBps) to " + "endBps (25, 50 or 100) over minutes (at most 30). Cannot be combined with creatorFeeBps unless the tier is 50. " + "Omit for the standard 50 bps pool"),
43623
- poolOpen: exports_external.enum(["legacy", "curve_price"]).optional().describe(`Solana preset tiers only. "curve_price" opens the pool at the curve's last price (the whole reserve is ` + "seeded and the remaining base burned); stakerAllocationBps plus the team allocation must fit the tier's limit, " + "which the dry run returns as graduation.maxStakerCarveBps. Omitted defaults to curve_price when those " + "allocations fit, otherwise falls back to legacy without refusal. Set legacy explicitly to opt out. " + "Explicit curve_price is refused when the allocations exceed the limit"),
43624
- expPoolShape: exports_external.enum(["floor", "full_range"]).optional().describe('Solana exp- curves only. "full_range" opens the pool across the whole price range. Omit for floor'),
43625
- dexVersion: exports_external.string().nullable().optional().describe('"v3" or "v4"; required for hood launches'),
43626
- buyAmount: exports_external.union([exports_external.string(), exports_external.number()]).optional().describe("Initial buy, in quote base units (string or number)"),
43627
- description: exports_external.string().optional(),
43628
- socials: exports_external.record(exports_external.string()).optional().describe("Social links keyed by platform"),
43629
- visibility: exports_external.string().optional().describe('"production", "test", "local", or "hidden"'),
43630
- dryRun: exports_external.boolean().optional().describe("Validate without executing the launch")
43631
- };
43632
44510
  getMarketShape = {
43633
44511
  chain: exports_external.string().describe('"solana" or "hood"'),
43634
44512
  mint: exports_external.string().describe("Token mint (solana) or contract address (hood)")
@@ -43734,20 +44612,16 @@ var init_tools = __esm(() => {
43734
44612
  clientTradeId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result. Retrying with the " + "SAME id is safe (idempotent replay); a new id is a SECOND trade."),
43735
44613
  paper: exports_external.preprocess((value) => value === "true" || value === 1 || value === "1" ? true : value === "false" || value === 0 || value === "0" ? false : value, exports_external.boolean().optional().describe("Rehearse instead of trading. The request passes every admission rule a live trade passes " + "-- the same planner, spend gate, key cap and loss limits -- and records the quote, but " + "nothing is ever broadcast and no funds move. Use it to check that a strategy is admitted " + "before risking anything on it. A paper fill is optimistic by construction: it books the " + "quoted price, so the gap between a paper arm and a live one IS the execution cost. " + "A sell of a mint this key already paper-bought also closes that paper book when the " + "live wallet is empty, even if this flag is omitted."))
43736
44614
  };
43737
- ({ buyAmount: _rawBuyAmount, ...seedableLaunchShape } = launchTokenShape);
43738
- seedShape = {
43739
- clientLaunchId: exports_external.string().optional().describe("Idempotency key. Auto-generated when omitted and echoed in the result."),
43740
- devBuy: exports_external.string().optional().describe('Seed buy in DECIMAL units of the quote asset this launch selects (e.g. "0.25" SOL, or ' + "ETH on hood), paid from the account's embedded wallet; the tool's description says how " + "each chain sends it. Follows quoteAsset, which defaults to sol on solana and eth on hood. " + "Capped by the platform dev-buy ceiling; for a larger seed, launch then follow with candle_trade.")
43741
- };
43742
- launchAndSeedShape = { ...seedableLaunchShape, ...seedShape };
44615
+ tokenImageUrl = launchSchemaField(launchTokenShape, "imageUrl");
44616
+ tokenBannerUrl = launchSchemaField(launchTokenShape, "bannerUrl");
43743
44617
  localImageShape = {
43744
- imageUrl: launchTokenShape.imageUrl.optional().describe(`${launchTokenShape.imageUrl.description}. Pass this or imagePath, never both`),
44618
+ imageUrl: tokenImageUrl.optional().describe(`${tokenImageUrl.description}. Pass this or imagePath, never both`),
43745
44619
  imagePath: exports_external.string().optional().describe("An absolute image path inside CANDLE_IMAGE_DIR. The file is uploaded and becomes public and permanent, " + "including on a dry run. PNG, JPEG, GIF or WebP, at most 10MB, read from the file's first bytes. " + "Uploading spends no funds and does not count against the launch cap; only the upload limit applies. " + "Pass this or imageUrl, never both. The result carries the imageUrl used: pass that on a retry, or " + "after a dry run, instead of uploading the file again. This is the square token image: Candle crops " + "it to a square, so a wide banner belongs in bannerPath, not here"),
43746
- bannerUrl: launchTokenShape.bannerUrl.describe(`${launchTokenShape.bannerUrl.description}. Pass this or bannerPath, never both`),
44620
+ bannerUrl: tokenBannerUrl.describe(`${tokenBannerUrl.description}. Pass this or bannerPath, never both`),
43747
44621
  bannerPath: exports_external.string().optional().describe("An absolute path, inside CANDLE_IMAGE_DIR, to WIDE banner artwork for the token page strip (wider than " + "1.5:1, e.g. 2171x724 or 1200x630). The file is uploaded uncropped and becomes public and permanent, " + "including on a dry run; PNG, JPEG, GIF or WebP, at most 10MB. A square image is refused with " + "BANNER_WRONG_SHAPE: send the wide original, not a cropped copy. Pass this or bannerUrl, never both. " + "The result carries the bannerUrl used: pass that on a retry instead of uploading the file again")
43748
44622
  };
43749
44623
  localLaunchTokenShape = { ...launchTokenShape, ...localImageShape };
43750
- localLaunchAndSeedShape = { ...seedableLaunchShape, ...localImageShape, ...seedShape };
44624
+ localLaunchAndSeedShape = { ...launchAndSeedShape, ...localImageShape };
43751
44625
  IMAGE_PATH_DISCLOSURE = " With imagePath or bannerPath, the file is uploaded first and becomes public and permanent, on a dry run too; " + "nothing about a dry run keeps that file private. imagePath is the square token image (Candle crops it); " + "a wide banner goes in bannerPath, which is not cropped.";
43752
44626
  transferShape = {
43753
44627
  chain: exports_external.enum(["solana", "hood"]).describe("Which chain the transfer executes on"),
@@ -72441,7 +73315,7 @@ var LAUNCH_USAGE = `candle launch ${launchFlagFields.map((field) => {
72441
73315
  if (field.key === "imageUrl")
72442
73316
  return `--image <path>|${invocation}`;
72443
73317
  return "required" in field && field.required ? invocation : `[${invocation}]`;
72444
- }).join(" ")} [--yes] [--rpc-url <url>] [--json]`;
73318
+ }).join(" ")} [--check] [--yes] [--rpc-url <url>] [--json]`;
72445
73319
  function launchFieldProblem(flags) {
72446
73320
  for (const field of launchFlagFields) {
72447
73321
  const value = flags[field.cli.flag];
@@ -72966,7 +73840,7 @@ var HELP = {
72966
73840
  launch: {
72967
73841
  group: "Trade",
72968
73842
  summary: "Create a token on Solana or Hood, with an optional first buy",
72969
- description: "Creates a token from the wallet you name, with an optional first buy. Embedded launches use /launch/headless on every plan, with the key's embedded-wallet allowance and launch:write; no swap:write is needed. A Solana first buy is a creator bundle, signed by Candle. A Hood first buy is best effort after creation. TEE launches use /launch/self and require Pro or Max, allowLaunch, a bound key and verified authority. TEE buys need TEE_LAUNCH_BUYS_ENABLED on the server; Solana buys also need AGENT_TRADING_ENABLED and Jito. Existing server gates and ceilings apply. The embedded path previews the launch before confirmation; --dry-run stops at the preview. No buy creates only the token. Local image and banner uploads happen before confirmation and are public and permanent even if the launch is declined. Reuse --client-trade-id for recovery.",
73843
+ description: "Creates a token from the wallet you name, with an optional first buy. Embedded launches use /launch/headless on every plan, with the key's embedded-wallet allowance and launch:write; no swap:write is needed. A Solana first buy is a creator bundle, signed by Candle. A Hood first buy is best effort after creation. TEE launches use /launch/self and require Pro or Max, allowLaunch, a bound key and verified authority. TEE buys need TEE_LAUNCH_BUYS_ENABLED on the server; Solana buys also need AGENT_TRADING_ENABLED and Jito. Existing server gates and ceilings apply. The embedded path previews the launch before confirmation; --dry-run stops at the preview. No buy creates only the token. Local image and banner uploads happen before confirmation and are public and permanent even if the launch is declined. Reuse --client-trade-id for recovery. --check answers first, before any of it: which launch path works on this key and server (embedded allowance, tier, allowLaunch, TEE_LAUNCH_BUYS_ENABLED) in one call, with nothing uploaded, built or sent; name a wallet with --wallet to narrow it, and it exits 1 when no path works.",
72970
73844
  usage: [LAUNCH_USAGE],
72971
73845
  rows: [],
72972
73846
  flags: [
@@ -72975,6 +73849,10 @@ var HELP = {
72975
73849
  invocation: "--image <path>",
72976
73850
  description: "Upload a local PNG, JPEG, GIF or WebP (at most 10MB) instead of --image-url; public and permanent before confirmation."
72977
73851
  },
73852
+ {
73853
+ invocation: "--check",
73854
+ description: "Answer which launch path works (embedded allowance, tier, allowLaunch, TEE_LAUNCH_BUYS_ENABLED) in one call, before any image upload; nothing is sent. Exits 1 when no path works."
73855
+ },
72978
73856
  { invocation: "--yes", description: "Confirm the launch without an interactive prompt." },
72979
73857
  {
72980
73858
  invocation: "--rpc-url <url>",
@@ -72982,6 +73860,8 @@ var HELP = {
72982
73860
  }
72983
73861
  ],
72984
73862
  examples: [
73863
+ "candle launch --check",
73864
+ "candle launch --check --wallet AgentOne",
72985
73865
  "candle launch --name Demo --symbol DEMO --image ./logo.png --wallet AgentOne",
72986
73866
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet AgentOne",
72987
73867
  "candle launch --name Demo --symbol DEMO --image-url https://example.com/d.png --wallet HoodAgent --dex-version v4",
@@ -73765,6 +74645,8 @@ function renderTopLevel(style) {
73765
74645
  const out = [
73766
74646
  "candle: Candle from the terminal: trade, launch, and hold your keys on your own machine",
73767
74647
  "",
74648
+ ...LAUNCH_DEFINITION.surfacesIntro,
74649
+ "",
73768
74650
  "Usage: candle <command> [<subcommand>] [flags]",
73769
74651
  " candle help <command> a command's subcommands, flags and examples"
73770
74652
  ];
@@ -73815,6 +74697,8 @@ function renderPresentedTopLevel(style) {
73815
74697
  const out = [
73816
74698
  ...brandHeader(style),
73817
74699
  ...!style.styled ? [...wrapText("Candle CLI: Your wallets and agent tools, one command away.", width), ""] : [],
74700
+ ...LAUNCH_DEFINITION.surfacesIntro.flatMap((line) => wrapText(line, width)),
74701
+ "",
73818
74702
  heading2("Start here"),
73819
74703
  ...renderRows([
73820
74704
  { invocation: "candle setup", description: "Connect your account" },
@@ -85576,24 +86460,25 @@ async function externalSweep(args, ctx) {
85576
86460
 
85577
86461
  // src/commands/fees.ts
85578
86462
  init_args();
86463
+ init_presentation();
85579
86464
  init_render();
85580
86465
  init_trading();
85581
86466
  init_swap();
85582
86467
  var LIST_USAGE = "Usage: candle fees [--cursor <cursor>] [--limit <1-50>] [--json]";
85583
86468
  var CLAIM_USAGE = "Usage: candle fees claim <mint> [--dry-run] [--json]";
85584
- function amountText(row) {
86469
+ function amountText(row, missing) {
85585
86470
  const raw = typeof row.amountRaw === "string" && /^\d+$/.test(row.amountRaw) ? row.amountRaw : null;
85586
86471
  if (raw === null)
85587
- return "unknown";
86472
+ return missing;
85588
86473
  const decimals = Number.isInteger(row.decimals) ? row.decimals : 0;
85589
86474
  const label = row.symbol ? row.symbol : row.mint ?? "";
85590
86475
  const value = decimalAmount(raw, decimals);
85591
86476
  return `${value.includes(".") ? value.replace(/\.?0+$/, "") : value} ${safeText(label)}`.trim();
85592
86477
  }
85593
- function amountsText(rows) {
86478
+ function amountsText(rows, missing) {
85594
86479
  if (!Array.isArray(rows) || rows.length === 0)
85595
86480
  return "none";
85596
- return rows.map((row) => amountText(row)).join(", ");
86481
+ return rows.map((row) => amountText(row, missing)).join(", ");
85597
86482
  }
85598
86483
  var REASON_WORDS = {
85599
86484
  below_dust: "under the dust floor",
@@ -85603,8 +86488,8 @@ var REASON_WORDS = {
85603
86488
  not_recipient: "the fees go to someone else, or the token has no fee vault",
85604
86489
  insufficient_gas: "the recipient wallet cannot cover the network fee"
85605
86490
  };
85606
- function reasonText(reason) {
85607
- return typeof reason === "string" ? REASON_WORDS[reason] ?? safeText(reason) : "unknown";
86491
+ function reasonText(reason, missing) {
86492
+ return typeof reason === "string" ? REASON_WORDS[reason] ?? safeText(reason) : missing;
85608
86493
  }
85609
86494
  async function feesList(args, ctx) {
85610
86495
  const parsed = parseArgs(args, { valueFlags: ["--cursor", "--limit"] });
@@ -85629,19 +86514,30 @@ async function feesList(args, ctx) {
85629
86514
  if (ctx.json)
85630
86515
  return printTradingResult(ctx, body);
85631
86516
  const page = Array.isArray(body.page) ? body.page : [];
85632
- const out = ctx.deps.stdout;
86517
+ const style = terminalStyle(ctx.deps, "stdout");
86518
+ const missing = symbols(style).missing;
85633
86519
  if (page.length === 0)
85634
- out.write(`No launched tokens on this account.
85635
- `);
85636
- for (const row of page) {
85637
- const name2 = safeText(String(row.symbol ?? row.name ?? row.mint));
85638
- const status = row.unreadable ? "could not be read" : row.claimable ? `claimable: ${amountsText(row.claimableAmounts)}` : `not claimable: ${reasonText(row.reason)}`;
85639
- out.write(`${name2} ${safeText(String(row.chain))} ${safeText(String(row.mint))} ${status}
86520
+ notice(ctx.deps, "stdout", "note", "No launched tokens on this account.");
86521
+ else {
86522
+ const rows = page.map((row) => {
86523
+ const status = row.unreadable ? "could not be read" : row.claimable ? `claimable: ${amountsText(row.claimableAmounts, missing)}` : `not claimable: ${reasonText(row.reason, missing)}`;
86524
+ return [
86525
+ safeText(String(row.symbol ?? row.name ?? row.mint)),
86526
+ safeText(String(row.chain)),
86527
+ status,
86528
+ safeText(String(row.mint))
86529
+ ];
86530
+ });
86531
+ ctx.deps.stdout.write(`${renderTable(["TOKEN", "CHAIN", "STATUS", "MINT"], rows, {
86532
+ style,
86533
+ width: style.width,
86534
+ indent: 2,
86535
+ columns: [{}, { priority: 1 }, {}, { id: true }]
86536
+ })}
85640
86537
  `);
85641
86538
  }
85642
86539
  if (body.isDone === false && typeof body.continueCursor === "string")
85643
- out.write(`More: candle fees --cursor ${safeText(body.continueCursor)}
85644
- `);
86540
+ notice(ctx.deps, "stdout", "note", `More: candle fees --cursor ${safeText(body.continueCursor)}`);
85645
86541
  return 0;
85646
86542
  } catch (error) {
85647
86543
  return tradingFailure(ctx, error);
@@ -85664,26 +86560,40 @@ async function feesClaim(args, ctx) {
85664
86560
  const body = await request(ctx, key, `/api/v1/agent/fees/${encodeURIComponent(mint)}/claim`, { dryRun });
85665
86561
  if (ctx.json)
85666
86562
  return printTradingResult(ctx, body);
85667
- const out = ctx.deps.stdout;
86563
+ const style = terminalStyle(ctx.deps, "stdout");
86564
+ const missing = symbols(style).missing;
85668
86565
  const claimed = Array.isArray(body.claimed) ? body.claimed : [];
85669
- if (dryRun) {
85670
- out.write(body.claimable === false ? `Dry run: would be refused: ${reasonText(body.reason)}. Nothing was sent.
85671
- ` : `Dry run: would claim ${amountsText(claimed)} to ${safeText(String(body.recipient))}. Nothing was sent.
86566
+ if (dryRun && body.claimable === false) {
86567
+ ctx.deps.stdout.write(`${failure(`Would be refused: ${reasonText(body.reason, missing)}.`, style)}
86568
+ `);
86569
+ ctx.deps.stdout.write(`${fields([["Still", amountsText(body.remaining, missing)]], style).join(`
86570
+ `)}
85672
86571
  `);
85673
86572
  } else {
85674
- out.write(`Claimed ${amountsText(claimed)} to ${safeText(String(body.recipient))}
86573
+ const rows = [
86574
+ [dryRun ? "Would claim" : "Amount", amountsText(claimed, missing)],
86575
+ ["To", safeText(String(body.recipient))],
86576
+ ["Still", amountsText(body.remaining, missing)]
86577
+ ];
86578
+ ctx.deps.stdout.write(`${heading(dryRun ? "Dry run" : "Claimed", style)}
86579
+
86580
+ ${fields(rows, style).join(`
86581
+ `)}
85675
86582
  `);
85676
- for (const row of claimed)
85677
- if (row.txHash)
85678
- out.write(` ${safeText(row.txHash)}
86583
+ if (!dryRun) {
86584
+ for (const row of claimed) {
86585
+ if (row.txHash)
86586
+ ctx.deps.stdout.write(`${bold(safeText(row.txHash), style)}
85679
86587
  `);
86588
+ }
86589
+ }
85680
86590
  }
85681
- out.write(`Still claimable: ${amountsText(body.remaining)}
85682
- `);
85683
- if (Array.isArray(body.warnings))
86591
+ if (dryRun)
86592
+ notice(ctx.deps, "stdout", "note", "Nothing was sent.");
86593
+ if (Array.isArray(body.warnings)) {
85684
86594
  for (const warning of body.warnings)
85685
- ctx.deps.stderr.write(`Warning: ${safeText(String(warning))}
85686
- `);
86595
+ notice(ctx.deps, "stderr", "warning", safeText(String(warning)));
86596
+ }
85687
86597
  return 0;
85688
86598
  } catch (error) {
85689
86599
  return tradingFailure(ctx, error);
@@ -86342,6 +87252,163 @@ function readLaunchBundle(args) {
86342
87252
  return { lamports: launch.lamports + buy.lamports, quoteRaw, feeRaw };
86343
87253
  }
86344
87254
 
87255
+ // src/launch-check.ts
87256
+ init_zod();
87257
+ init_trading();
87258
+ var checkSchema = exports_external.object({
87259
+ key: exports_external.object({
87260
+ keyPrefix: exports_external.string(),
87261
+ scopes: exports_external.array(exports_external.string()),
87262
+ tier: exports_external.string(),
87263
+ selfLaunch: exports_external.boolean(),
87264
+ embeddedWalletPermission: exports_external.enum(["allowed", "denied"])
87265
+ }),
87266
+ switches: exports_external.object({
87267
+ headlessLaunch: exports_external.boolean(),
87268
+ agentTrading: exports_external.boolean(),
87269
+ teeLaunchBuys: exports_external.boolean()
87270
+ }),
87271
+ embedded: exports_external.object({
87272
+ solana: exports_external.object({ address: exports_external.string(), delegated: exports_external.boolean().optional() }).nullable().optional(),
87273
+ evm: exports_external.object({ address: exports_external.string(), delegated: exports_external.boolean().optional() }).nullable().optional()
87274
+ }).optional(),
87275
+ wallets: exports_external.array(exports_external.object({
87276
+ id: exports_external.string(),
87277
+ address: exports_external.string(),
87278
+ chain: exports_external.string(),
87279
+ label: exports_external.string().nullable(),
87280
+ allowLaunch: exports_external.boolean(),
87281
+ active: exports_external.boolean()
87282
+ }))
87283
+ }).passthrough();
87284
+ var EMBEDDED_NAMES = new Set(LAUNCH_DEFINITION.wallet.embeddedNames);
87285
+ function walletName(row) {
87286
+ return row.label ?? row.id;
87287
+ }
87288
+ function walletLines(answer2, row, requestedChain) {
87289
+ const name2 = walletName(row);
87290
+ const chain2 = row.chain === "evm" ? "Hood" : "Solana";
87291
+ const lines = [];
87292
+ if (requestedChain && rowChain(row) !== requestedChain)
87293
+ return [
87294
+ `${name2} (${chain2}) cannot launch on ${chainName(requestedChain)}: choose a ${chainName(requestedChain)} wallet.`
87295
+ ];
87296
+ const gated = gatedOff(answer2);
87297
+ if (gated) {
87298
+ lines.push(`${name2} (${chain2}): ${gated}`);
87299
+ return lines;
87300
+ }
87301
+ if (!answer2.key.selfLaunch)
87302
+ lines.push(`${name2} (${chain2}) cannot launch: TEE launches need Pro or Max, and this key is ${answer2.key.tier}.`);
87303
+ else if (!row.allowLaunch)
87304
+ lines.push(`${name2} (${chain2}) cannot launch: the account owner has allowLaunch off for it. Run candle wallets allow-launch ${row.id} on a profile signed in with candle auth login.`);
87305
+ else if (!row.active)
87306
+ lines.push(`${name2} (${chain2}) cannot launch: it is not a verified-active TEE wallet.`);
87307
+ else {
87308
+ lines.push(`This key can launch from ${name2} with no first buy.`);
87309
+ if (!answer2.switches.teeLaunchBuys)
87310
+ lines.push(`A first buy from ${name2} is off on this server; launch with no first buy, then buy with candle swap.`);
87311
+ else if (chain2 === "Solana" && !answer2.switches.agentTrading)
87312
+ lines.push(`A first buy from ${name2} is off on this server (AGENT_TRADING_ENABLED off for Solana first buys).`);
87313
+ else
87314
+ lines.push(`A first buy from ${name2} rides the launch (TEE_LAUNCH_BUYS_ENABLED is on).`);
87315
+ }
87316
+ return lines;
87317
+ }
87318
+ function gatedOff(answer2) {
87319
+ if (!answer2.key.scopes.includes("launch:write"))
87320
+ return "this key lacks launch:write, so no launch path works under it.";
87321
+ if (!answer2.switches.headlessLaunch)
87322
+ return "launches are off on this server (HEADLESS_LAUNCH_ENABLED off).";
87323
+ return;
87324
+ }
87325
+ function embeddedLines(answer2, requestedChain) {
87326
+ const lines = [];
87327
+ const gated = gatedOff(answer2);
87328
+ if (answer2.key.embeddedWalletPermission === "denied") {
87329
+ lines.push("The embedded wallet is denied on this key.");
87330
+ return lines;
87331
+ }
87332
+ const paths = embeddedPaths(answer2, requestedChain);
87333
+ if (!paths.some((path) => path.wallet)) {
87334
+ lines.push(`This account has no ${requestedChain ? `${chainName(requestedChain)} ` : ""}embedded wallet. Create one in the app, or launch from a TEE wallet with --wallet <tee>.`);
87335
+ return lines;
87336
+ }
87337
+ if (gated) {
87338
+ lines.push(`Embedded wallet: ${gated}`);
87339
+ return lines;
87340
+ }
87341
+ for (const { chain: chain2, wallet: wallet3 } of paths) {
87342
+ if (!wallet3)
87343
+ continue;
87344
+ if (wallet3.delegated === false) {
87345
+ lines.push(`The account's ${chainName(chain2)} embedded wallet is not delegated for agent access.`);
87346
+ continue;
87347
+ }
87348
+ lines.push(`This key can launch from the ${chainName(chain2)} embedded wallet with no first buy.`);
87349
+ if (chain2 === "solana" && !answer2.switches.agentTrading)
87350
+ lines.push("A Solana first buy from the embedded wallet is off on this server (AGENT_TRADING_ENABLED off).");
87351
+ else
87352
+ lines.push(chain2 === "solana" ? "A Solana first buy can ride the launch as one bundle signed by Candle." : "A Hood first buy can ride the launch best effort after creation.");
87353
+ }
87354
+ return lines;
87355
+ }
87356
+ function walletWorks(answer2, row, requestedChain) {
87357
+ return (requestedChain === undefined || rowChain(row) === requestedChain) && gatedOff(answer2) === undefined && answer2.key.selfLaunch && row.allowLaunch && row.active;
87358
+ }
87359
+ function embeddedPaths(answer2, requestedChain) {
87360
+ return (requestedChain ? [requestedChain] : ["solana", "hood"]).map((chain2) => ({
87361
+ chain: chain2,
87362
+ wallet: chain2 === "solana" ? answer2.embedded?.solana : answer2.embedded?.evm
87363
+ }));
87364
+ }
87365
+ function embeddedWorks(answer2, requestedChain) {
87366
+ return gatedOff(answer2) === undefined && answer2.key.embeddedWalletPermission === "allowed" && embeddedPaths(answer2, requestedChain).some(({ wallet: wallet3 }) => wallet3 != null && wallet3.delegated !== false);
87367
+ }
87368
+ function describeRow(row) {
87369
+ return `${row.label ? `${row.label} ` : ""}(${row.id}, ${row.address})`;
87370
+ }
87371
+ async function launchCheck(ctx, key, walletFlag, requestedChain) {
87372
+ const answer2 = checkSchema.parse(await request(ctx, key, "/api/v1/launch/preflight"));
87373
+ const embeddedOnly = walletFlag !== undefined && EMBEDDED_NAMES.has(walletFlag);
87374
+ const named = walletFlag === undefined || embeddedOnly ? [] : answer2.wallets.filter((row) => row.id === walletFlag || row.address === walletFlag || row.label === walletFlag);
87375
+ if (walletFlag !== undefined && !embeddedOnly && named.length !== 1)
87376
+ throw new TradingError("TEE_WALLET_REQUIRED", named.length > 1 ? `"${walletFlag}" matches ${named.length} TEE wallets on this key: ${named.map(describeRow).join("; ")}. Name one by id or address.` : answer2.wallets.length === 0 ? "This key has no TEE wallets bound to it. Enrol one, or select the profile whose key holds it." : `No TEE wallet on this key is called "${walletFlag}". Bound to this key: ${answer2.wallets.map(describeRow).join("; ")}.`);
87377
+ const shownWallets = walletFlag === undefined ? answer2.wallets : named;
87378
+ const lines = [`Launch paths for key ${answer2.key.keyPrefix} (tier ${answer2.key.tier}):`];
87379
+ if (requestedChain)
87380
+ lines.push(`Requested launch chain: ${chainName(requestedChain)}.`);
87381
+ if (walletFlag === undefined || embeddedOnly)
87382
+ lines.push(...embeddedLines(answer2, requestedChain));
87383
+ for (const row of shownWallets)
87384
+ lines.push(...walletLines(answer2, row, requestedChain));
87385
+ const works = (walletFlag === undefined || embeddedOnly ? embeddedWorks(answer2, requestedChain) : false) || shownWallets.some((row) => walletWorks(answer2, row, requestedChain));
87386
+ lines.push(works ? "Nothing was uploaded, built or sent; this was the whole check." : "No launch path works for this key. Nothing was uploaded, built or sent.");
87387
+ const result = {
87388
+ success: true,
87389
+ check: true,
87390
+ works,
87391
+ ...requestedChain ? { requestedChain } : {},
87392
+ key: answer2.key,
87393
+ switches: answer2.switches,
87394
+ ...answer2.embedded ? { embedded: answer2.embedded } : {},
87395
+ wallets: shownWallets.map((row) => ({
87396
+ ...row,
87397
+ canLaunch: walletWorks(answer2, row, requestedChain),
87398
+ canBuy: walletWorks(answer2, row, requestedChain) && answer2.switches.teeLaunchBuys && (row.chain !== "solana" || answer2.switches.agentTrading)
87399
+ })),
87400
+ ...embeddedOnly || walletFlag === undefined ? { embeddedCanLaunch: embeddedWorks(answer2, requestedChain) } : {}
87401
+ };
87402
+ if (ctx.json)
87403
+ ctx.deps.stdout.write(`${JSON.stringify(result)}
87404
+ `);
87405
+ else
87406
+ ctx.deps.stdout.write(`${lines.join(`
87407
+ `)}
87408
+ `);
87409
+ return works ? 0 : 1;
87410
+ }
87411
+
86345
87412
  // src/commands/launch.ts
86346
87413
  init_render();
86347
87414
  init_solana_endpoint();
@@ -86400,6 +87467,7 @@ var LAUNCH_ARG_SPEC = {
86400
87467
  ],
86401
87468
  booleanFlags: [
86402
87469
  ...launchFlagFields.filter((field) => field.type === "boolean").map((field) => field.cli.flag),
87470
+ "--check",
86403
87471
  "--yes"
86404
87472
  ],
86405
87473
  pathFlags: ["--image", "--banner"]
@@ -86497,6 +87565,11 @@ async function launch(args, ctx) {
86497
87565
  const id = flags["--client-trade-id"] ?? `launch-${randomUUID5()}`;
86498
87566
  const embedded = EMBEDDED_WALLET_NAMES.has(flags["--wallet"] ?? "");
86499
87567
  const dryRun = parsed.booleans.has("--dry-run");
87568
+ const check = parsed.booleans.has("--check");
87569
+ if (check && dryRun) {
87570
+ writeUsageFailure(ctx.deps, `--check and --dry-run are two different stops; pass one. ${USAGE6}`, ctx.json);
87571
+ return 2;
87572
+ }
86500
87573
  const fieldProblem = launchFieldProblem(flags);
86501
87574
  if (fieldProblem) {
86502
87575
  writeUsageFailure(ctx.deps, fieldProblem, ctx.json);
@@ -86519,7 +87592,8 @@ async function launch(args, ctx) {
86519
87592
  writeUsageFailure(ctx.deps, "--banner takes a local file or an https:// URL.", ctx.json);
86520
87593
  return 2;
86521
87594
  }
86522
- if (parsed.positionals.length || !flags["--name"] || !flags["--symbol"] || flags["--image"] === undefined && flags["--image-url"] === undefined || !flags["--wallet"] || !validClientId(id)) {
87595
+ const missingRequired = !flags["--name"] || !flags["--symbol"] || flags["--image"] === undefined && flags["--image-url"] === undefined || !flags["--wallet"];
87596
+ if (parsed.positionals.length || !check && missingRequired || !validClientId(id)) {
86523
87597
  writeUsageFailure(ctx.deps, USAGE6, ctx.json);
86524
87598
  return 2;
86525
87599
  }
@@ -86533,21 +87607,25 @@ async function launch(args, ctx) {
86533
87607
  if (flags["--buy"]?.trim() && !/^0+(\.0+)?$/.test(flags["--buy"]))
86534
87608
  launchBuyRaw(flags["--buy"], 18);
86535
87609
  const hinted = flagChain(flags);
86536
- if (hinted === "hood" && flags["--dex-version"] === undefined) {
87610
+ if (!check && hinted === "hood" && flags["--dex-version"] === undefined) {
86537
87611
  writeUsageFailure(ctx.deps, `A Hood launch needs --dex-version v3|v4. ${USAGE6}`, ctx.json);
86538
87612
  return 2;
86539
87613
  }
86540
87614
  let image;
86541
87615
  let banner;
86542
- try {
86543
- if (flags["--image"] !== undefined)
86544
- image = await readLocalImage(flags["--image"]);
86545
- if (flags["--banner"] && !flags["--banner"].startsWith("https://"))
86546
- banner = await readLocalImage(flags["--banner"]);
86547
- } catch (error) {
86548
- return tradingFailure(ctx, error);
87616
+ if (!check) {
87617
+ try {
87618
+ if (flags["--image"] !== undefined)
87619
+ image = await readLocalImage(flags["--image"]);
87620
+ if (flags["--banner"] && !flags["--banner"].startsWith("https://"))
87621
+ banner = await readLocalImage(flags["--banner"]);
87622
+ } catch (error) {
87623
+ return tradingFailure(ctx, error);
87624
+ }
86549
87625
  }
86550
87626
  const key = await tradingKey(ctx);
87627
+ if (check)
87628
+ return await launchCheck(ctx, key, flags["--wallet"], hinted);
86551
87629
  const prior = dryRun ? null : await lookupOperation(ctx, key, id, "launch");
86552
87630
  let retry;
86553
87631
  if (prior) {
@@ -86584,7 +87662,7 @@ async function launch(args, ctx) {
86584
87662
  yes: parsed.booleans.has("--yes"),
86585
87663
  dryRun
86586
87664
  });
86587
- const wallet3 = await tradingWallet(ctx, key, flags["--wallet"], "launch:write", hinted ?? "any");
87665
+ const wallet3 = await tradingWallet(ctx, key, flags["--wallet"] ?? "", "launch:write", hinted ?? "any");
86588
87666
  const chain2 = wallet3.chain === "evm" ? "hood" : "solana";
86589
87667
  if (chain2 === "hood" && flags["--dex-version"] === undefined)
86590
87668
  throw new TradingUsage(`${wallet3.address} is a Hood TEE wallet, and a Hood launch needs --dex-version v3|v4.`);
@@ -86672,7 +87750,7 @@ async function launch(args, ctx) {
86672
87750
  const result = await request(ctx, key, "/api/v1/launch/self/confirm", { clientLaunchId: id, signature });
86673
87751
  return printTradingResult(ctx, { ...result, clientTradeId: id, kind: "launch", quote, ...used });
86674
87752
  } catch (error) {
86675
- return tradingFailure(ctx, error, dryRun ? undefined : id);
87753
+ return tradingFailure(ctx, error, dryRun || check ? undefined : id);
86676
87754
  }
86677
87755
  }
86678
87756
  async function uploadLaunchImages(ctx, key, args) {
@@ -90450,8 +91528,54 @@ function isParsedClearinghouse2(value) {
90450
91528
  }
90451
91529
 
90452
91530
  // src/commands/perps-risk.ts
91531
+ init_presentation();
90453
91532
  init_render();
90454
91533
  init_trading();
91534
+
91535
+ // src/usd.ts
91536
+ init_render();
91537
+ var COMPACT_UNITS = [
91538
+ [1000000000000, "T"],
91539
+ [1e9, "B"],
91540
+ [1e6, "M"]
91541
+ ];
91542
+ function formatUsd(value, options = {}) {
91543
+ if (!Number.isFinite(value))
91544
+ return "?";
91545
+ const sign4 = value < 0 ? "-" : options.signed && value > 0 ? "+" : "";
91546
+ const abs = Math.abs(value);
91547
+ if (abs > 0 && abs < 0.005)
91548
+ return `${sign4}<$0.01`;
91549
+ if (options.compact) {
91550
+ for (const [unit, suffix] of COMPACT_UNITS) {
91551
+ if (Math.round(abs / (unit / 100)) >= 100)
91552
+ return `${sign4}$${(abs / unit).toFixed(2)}${suffix}`;
91553
+ }
91554
+ }
91555
+ return `${sign4}$${abs.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
91556
+ }
91557
+ function formatPrice(value) {
91558
+ if (!Number.isFinite(value) || value <= 0)
91559
+ return "?";
91560
+ if (value >= 1)
91561
+ return formatUsd(value);
91562
+ return `$${Number(value.toPrecision(4)).toLocaleString("en-US", { maximumFractionDigits: 12 })}`;
91563
+ }
91564
+ function formatAmount(raw, decimals) {
91565
+ const digits = BigInt(raw).toString().padStart(decimals + 1, "0");
91566
+ if (decimals === 0)
91567
+ return digits;
91568
+ const whole = digits.slice(0, -decimals);
91569
+ const fraction = digits.slice(-decimals).replace(/0+$/, "");
91570
+ return fraction ? `${whole}.${fraction}` : whole;
91571
+ }
91572
+ function formatQuantity(value) {
91573
+ if (!Number.isFinite(value))
91574
+ return "?";
91575
+ return value.toLocaleString("en-US", { maximumFractionDigits: 6 });
91576
+ }
91577
+
91578
+ // src/commands/perps-risk.ts
90455
91579
  init_keys();
90456
91580
  init_swap();
90457
91581
  init_tee_rebind();
@@ -90496,6 +91620,16 @@ var usd = (value) => {
90496
91620
  return Number.isFinite(n) ? n.toFixed(2) : safeText(value);
90497
91621
  };
90498
91622
  var pct = (fraction) => fraction === null ? "-" : `${(fraction * 100).toFixed(1)}%`;
91623
+ function signedPct(fraction, missing) {
91624
+ if (fraction === null || !Number.isFinite(fraction))
91625
+ return missing;
91626
+ const body = `${Math.abs(fraction * 100).toFixed(1)}%`;
91627
+ if (fraction > 0)
91628
+ return `+${body}`;
91629
+ if (fraction < 0)
91630
+ return `-${body}`;
91631
+ return body;
91632
+ }
90499
91633
  function describeDriver(d) {
90500
91634
  if (d.kind === "position") {
90501
91635
  if (d.reason === "unmarked")
@@ -90523,6 +91657,69 @@ function levelCell(row) {
90523
91657
  function accountText(line) {
90524
91658
  return `Account value ${usd(line.accountValueUsd)} USD (cross ${usd(line.crossAccountValueUsd)}), margin used ${usd(line.marginUsedUsd)}, free margin ${usd(line.freeMarginUsd)}, withdrawable ${usd(line.withdrawableUsd)}, margin ratio ${line.marginRatio === null ? "none (no cross equity)" : line.marginRatio.toFixed(4)}, unrealized ${usd(line.unrealizedPnlUsd)}, ${line.positionCount} ${line.positionCount === 1 ? "position" : "positions"}, ${safeText(line.mode)} mode`;
90525
91659
  }
91660
+ function usdField(value, missing, signed = false) {
91661
+ if (value === null || value === undefined || value === "")
91662
+ return missing;
91663
+ const n = Number(value);
91664
+ return Number.isFinite(n) ? formatUsd(n, { signed }) : safeText(value);
91665
+ }
91666
+ function riskPositionsTable(rows, style) {
91667
+ if (rows.length === 0)
91668
+ return "No open positions.";
91669
+ const missing = symbols(style).missing;
91670
+ const price = (value) => usdField(value, missing);
91671
+ return renderTable([
91672
+ "COIN",
91673
+ "SIDE",
91674
+ "SIZE",
91675
+ "ENTRY",
91676
+ "MARK",
91677
+ "UPNL",
91678
+ "ROE",
91679
+ "LEV",
91680
+ "LIQ",
91681
+ "DIST",
91682
+ "FUNDING PAID",
91683
+ "MARGIN",
91684
+ "VALUE",
91685
+ "LEVEL"
91686
+ ], rows.map((r) => [
91687
+ safeText(r.coin),
91688
+ safeText(r.side),
91689
+ safeText(r.size),
91690
+ price(r.entryPx),
91691
+ r.markPx === null ? missing : price(r.markPx),
91692
+ usdField(r.unrealizedPnlUsd, missing, true),
91693
+ signedPct(Number(r.returnOnEquity), missing),
91694
+ safeText(`${r.leverage.value}x ${r.leverage.type}`),
91695
+ r.liquidationPx === null ? missing : price(r.liquidationPx),
91696
+ r.liquidationDistance === null ? missing : pct(r.liquidationDistance),
91697
+ usdField(r.fundingSinceOpenUsd, missing, true),
91698
+ usdField(r.marginUsedUsd, missing),
91699
+ usdField(r.positionValueUsd, missing),
91700
+ levelCell(r)
91701
+ ]), {
91702
+ style,
91703
+ width: style.width,
91704
+ indent: 2,
91705
+ columns: [
91706
+ { id: true },
91707
+ {},
91708
+ {},
91709
+ { priority: 5 },
91710
+ {},
91711
+ { signed: true },
91712
+ { priority: 4, signed: true },
91713
+ { priority: 6 },
91714
+ {},
91715
+ {},
91716
+ { priority: 1, signed: true },
91717
+ { priority: 3 },
91718
+ { priority: 2 },
91719
+ {}
91720
+ ]
91721
+ });
91722
+ }
90526
91723
  function positionsTable(rows) {
90527
91724
  if (rows.length === 0)
90528
91725
  return "No open positions.";
@@ -90562,6 +91759,62 @@ function thresholdsText(t) {
90562
91759
  return `Thresholds (${t.source}): warn under ${(t.warnDistanceBps / 100).toFixed(1)}% or ratio above ${(t.warnMarginRatioBps / 1e4).toFixed(2)}; critical under ${(t.criticalDistanceBps / 100).toFixed(1)}% or above ${(t.criticalMarginRatioBps / 1e4).toFixed(2)}; clear band ${(t.clearBandBps / 100).toFixed(1)} points`;
90563
91760
  }
90564
91761
  var NO_MONITOR = "Until Candle's server-side monitor exists, nothing warns you unless you poll candle perps risk or run candle perps watch.";
91762
+ function writeRiskText(ctx, report) {
91763
+ const style = terminalStyle(ctx.deps, "stdout");
91764
+ const sym = symbols(style);
91765
+ const level = report.level.toUpperCase();
91766
+ const painted = report.level === "critical" ? failure(level, style) : report.level === "warn" ? accent(`${sym.warning} ${level}`, style, "warning") : success(level, style);
91767
+ const lead = report.drivers[0] ?? report.closest;
91768
+ const also = report.drivers.slice(1).map((driver) => describeDriver(driver));
91769
+ const account = report.account;
91770
+ const thresholds2 = report.thresholds;
91771
+ const lines = [
91772
+ painted,
91773
+ "",
91774
+ heading("Risk", style),
91775
+ "",
91776
+ ...fields([
91777
+ ["Driver", lead ? describeDriver(lead) : sym.missing],
91778
+ ...also.length > 0 ? [["Also", also.join(`
91779
+ `)]] : []
91780
+ ], style),
91781
+ "",
91782
+ heading("Account", style),
91783
+ "",
91784
+ ...account ? fields([
91785
+ ["Value", usdField(account.accountValueUsd, sym.missing)],
91786
+ ["Cross", usdField(account.crossAccountValueUsd, sym.missing)],
91787
+ ["Margin used", usdField(account.marginUsedUsd, sym.missing)],
91788
+ ["Free margin", usdField(account.freeMarginUsd, sym.missing)],
91789
+ ["Withdrawable", usdField(account.withdrawableUsd, sym.missing)],
91790
+ ["Margin ratio", account.marginRatio === null ? "none (no cross equity)" : account.marginRatio.toFixed(4)],
91791
+ ["Unrealized", usdField(account.unrealizedPnlUsd, sym.missing, true)],
91792
+ ["Positions", String(account.positionCount)],
91793
+ ["Mode", safeText(account.mode)]
91794
+ ], style) : [`${" ".repeat(2)}No account figures.`],
91795
+ "",
91796
+ riskPositionsTable(report.positions, style),
91797
+ "",
91798
+ heading("Thresholds", style),
91799
+ "",
91800
+ ...fields([
91801
+ ["Source", thresholds2.source],
91802
+ [
91803
+ "Warn",
91804
+ `under ${(thresholds2.warnDistanceBps / 100).toFixed(1)}% or ratio above ${(thresholds2.warnMarginRatioBps / 1e4).toFixed(2)}`
91805
+ ],
91806
+ [
91807
+ "Critical",
91808
+ `under ${(thresholds2.criticalDistanceBps / 100).toFixed(1)}% or above ${(thresholds2.criticalMarginRatioBps / 1e4).toFixed(2)}`
91809
+ ],
91810
+ ["Clear band", `${(thresholds2.clearBandBps / 100).toFixed(1)} points`]
91811
+ ], style)
91812
+ ];
91813
+ ctx.deps.stdout.write(`${lines.join(`
91814
+ `)}
91815
+ `);
91816
+ notice(ctx.deps, "stdout", "note", NO_MONITOR);
91817
+ }
90565
91818
  async function perpsRisk(args, ctx) {
90566
91819
  const parsed = parseArgs(args, { valueFlags: ["--wallet"] });
90567
91820
  if ("error" in parsed)
@@ -90588,22 +91841,8 @@ async function perpsRisk(args, ctx) {
90588
91841
  if (ctx.json)
90589
91842
  ctx.deps.stdout.write(`${JSON.stringify(report)}
90590
91843
  `);
90591
- else {
90592
- const out = ctx.deps.stdout;
90593
- out.write(`${riskLine(report)}
90594
- `);
90595
- for (const d of report.drivers.slice(1))
90596
- out.write(` also: ${describeDriver(d)}
90597
- `);
90598
- if (report.account)
90599
- out.write(`${accountText(report.account)}
90600
- `);
90601
- out.write(`${positionsTable(report.positions)}
90602
- `);
90603
- out.write(`${thresholdsText(report.thresholds)}
90604
- ${NO_MONITOR}
90605
- `);
90606
- }
91844
+ else
91845
+ writeRiskText(ctx, report);
90607
91846
  return perpsRiskExitCode(report.level);
90608
91847
  } catch (error) {
90609
91848
  return tradingFailure(ctx, error);
@@ -92600,9 +93839,75 @@ async function perpsReduceOnly(args, ctx) {
92600
93839
  // src/commands/perps-markets.ts
92601
93840
  init_args();
92602
93841
  init_deps();
93842
+ init_presentation();
92603
93843
  init_render();
92604
93844
  init_trading();
92605
93845
  init_swap();
93846
+ function marketUsd(value, missing, compact) {
93847
+ if (value === null)
93848
+ return missing;
93849
+ const n = Number(value);
93850
+ return Number.isFinite(n) ? formatUsd(n, { compact }) : missing;
93851
+ }
93852
+ function changeCell(value, missing) {
93853
+ if (value === null)
93854
+ return missing;
93855
+ const n = Number(value);
93856
+ if (!Number.isFinite(n))
93857
+ return safeText(value);
93858
+ const body = `${Math.abs(n).toFixed(1)}%`;
93859
+ if (n > 0)
93860
+ return `+${body}`;
93861
+ if (n < 0)
93862
+ return `-${body}`;
93863
+ return body;
93864
+ }
93865
+ function fundingCell(rate, missing) {
93866
+ if (rate === null)
93867
+ return missing;
93868
+ const n = Number(rate) * 100;
93869
+ return Number.isFinite(n) ? `${n.toFixed(4)}%` : missing;
93870
+ }
93871
+ function flagCell(row) {
93872
+ return [
93873
+ row.onlyIsolated ? "iso" : "",
93874
+ row.atOpenInterestCap ? "cap" : "",
93875
+ row.candle.tradeable === false ? `x:${row.candle.reason}` : row.candle.tradeable === null ? "?:networkMismatch" : ""
93876
+ ].filter(Boolean).join(",");
93877
+ }
93878
+ function writeMarkets(ctx, result) {
93879
+ const style = terminalStyle(ctx.deps, "stdout");
93880
+ const missing = symbols(style).missing;
93881
+ const rows = result.markets.map((row) => [
93882
+ safeText(row.coin),
93883
+ marketUsd(row.markPx, missing, false),
93884
+ changeCell(row.change24hPct, missing),
93885
+ fundingCell(row.fundingRate, missing),
93886
+ row.fundingAprPct === null ? missing : `${row.fundingAprPct}%`,
93887
+ marketUsd(row.openInterestUsd, missing, true),
93888
+ marketUsd(row.dayNotionalVolumeUsd, missing, true),
93889
+ `${row.maxLeverage}x`,
93890
+ flagCell(row)
93891
+ ]);
93892
+ ctx.deps.stdout.write(`${renderTable(["COIN", "MARK", "24H", "FUNDING/H", "APR", "OI", "24H VOL", "LEV", "FLAGS"], rows, {
93893
+ style,
93894
+ width: style.width,
93895
+ indent: 2,
93896
+ columns: [
93897
+ { id: true },
93898
+ {},
93899
+ { signed: true, priority: 4 },
93900
+ { priority: 3 },
93901
+ { priority: 5 },
93902
+ {},
93903
+ { priority: 2 },
93904
+ {},
93905
+ { priority: 1 }
93906
+ ]
93907
+ })}
93908
+ `);
93909
+ notice(ctx.deps, "stdout", "note", `Hyperliquid ${result.network}. OI floor ${formatUsd(result.gate.minOpenInterestUsd, { compact: true })} (${result.gate.source}). Tradeable now is Candle's build gate at read time, not a promise. OI caps may still refuse an order.`);
93910
+ }
92606
93911
  var flags = {
92607
93912
  markets: ["--sort", "--limit"],
92608
93913
  market: [],
@@ -92646,32 +93951,19 @@ async function read(ctx, argv, command) {
92646
93951
  `);
92647
93952
  return 0;
92648
93953
  }
93954
+ if ("markets" in result) {
93955
+ for (const warning of result.warnings)
93956
+ notice(ctx.deps, "stderr", "warning", safeText(warning));
93957
+ writeMarkets(ctx, result);
93958
+ return 0;
93959
+ }
92649
93960
  for (const warning of result.warnings)
92650
93961
  ctx.deps.stderr.write(`${safeText(warning)}
92651
93962
  `);
92652
93963
  const out = (line) => ctx.deps.stdout.write(`${line}
92653
93964
  `);
92654
93965
  out(`Hyperliquid ${result.network}; OI floor $${result.gate.minOpenInterestUsd} (${result.gate.source}). Tradeability is Candle's build gate at read time, not a promise. OI caps may still refuse an order.`);
92655
- const money = (n) => n === null ? "-" : Number(n).toFixed(2);
92656
- if ("markets" in result) {
92657
- out("COIN MARK USD 24H % FUNDING %/h APR % OI USD 24H VOLUME USD LEVERAGE FLAGS");
92658
- for (const row of result.markets)
92659
- out([
92660
- safeText(row.coin),
92661
- money(row.markPx),
92662
- row.change24hPct ?? "-",
92663
- row.fundingRate === null ? "-" : (Number(row.fundingRate) * 100).toFixed(4),
92664
- row.fundingAprPct ?? "-",
92665
- row.openInterestUsd ?? "-",
92666
- money(row.dayNotionalVolumeUsd),
92667
- `${row.maxLeverage}x`,
92668
- [
92669
- row.onlyIsolated ? "iso" : "",
92670
- row.atOpenInterestCap ? "cap" : "",
92671
- row.candle.tradeable === false ? `x:${row.candle.reason}` : row.candle.tradeable === null ? "?:networkMismatch" : ""
92672
- ].filter(Boolean).join(",")
92673
- ].join(" "));
92674
- } else if ("market" in result) {
93966
+ if ("market" in result) {
92675
93967
  out(JSON.stringify({ ...result.market, coin: safeText(result.market.coin) }, null, 2));
92676
93968
  } else if ("bids" in result) {
92677
93969
  out(`${safeText(result.coin)}: BID PX / SIZE / ORDERS | ASK PX / SIZE / ORDERS`);
@@ -93580,49 +94872,6 @@ init_presentation();
93580
94872
  init_profiles();
93581
94873
  init_render();
93582
94874
 
93583
- // src/usd.ts
93584
- init_render();
93585
- var COMPACT_UNITS = [
93586
- [1000000000000, "T"],
93587
- [1e9, "B"],
93588
- [1e6, "M"]
93589
- ];
93590
- function formatUsd(value, options = {}) {
93591
- if (!Number.isFinite(value))
93592
- return "?";
93593
- const sign5 = value < 0 ? "-" : options.signed && value > 0 ? "+" : "";
93594
- const abs = Math.abs(value);
93595
- if (abs > 0 && abs < 0.005)
93596
- return `${sign5}<$0.01`;
93597
- if (options.compact) {
93598
- for (const [unit, suffix] of COMPACT_UNITS) {
93599
- if (Math.round(abs / (unit / 100)) >= 100)
93600
- return `${sign5}$${(abs / unit).toFixed(2)}${suffix}`;
93601
- }
93602
- }
93603
- return `${sign5}$${abs.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
93604
- }
93605
- function formatPrice(value) {
93606
- if (!Number.isFinite(value) || value <= 0)
93607
- return "?";
93608
- if (value >= 1)
93609
- return formatUsd(value);
93610
- return `$${Number(value.toPrecision(4)).toLocaleString("en-US", { maximumFractionDigits: 12 })}`;
93611
- }
93612
- function formatAmount(raw, decimals) {
93613
- const digits = BigInt(raw).toString().padStart(decimals + 1, "0");
93614
- if (decimals === 0)
93615
- return digits;
93616
- const whole = digits.slice(0, -decimals);
93617
- const fraction = digits.slice(-decimals).replace(/0+$/, "");
93618
- return fraction ? `${whole}.${fraction}` : whole;
93619
- }
93620
- function formatQuantity(value) {
93621
- if (!Number.isFinite(value))
93622
- return "?";
93623
- return value.toLocaleString("en-US", { maximumFractionDigits: 6 });
93624
- }
93625
-
93626
94875
  // src/commands/financial-output.ts
93627
94876
  init_presentation();
93628
94877
  init_render();
@@ -98124,6 +99373,7 @@ async function tokenVampCandidates(args, ctx) {
98124
99373
  // src/commands/transfer.ts
98125
99374
  init_args();
98126
99375
  init_evm_lite();
99376
+ init_presentation();
98127
99377
  init_profiles();
98128
99378
  init_render();
98129
99379
  init_solana_verify();
@@ -98192,6 +99442,16 @@ function describeDestination(destination) {
98192
99442
  return `address ${destination.address} (not one of this account's linked wallets; Candle decides whether it is allowed)`;
98193
99443
  }
98194
99444
  }
99445
+ function writePreview(ctx, title, rows, note) {
99446
+ const style = terminalStyle(ctx.deps, "stderr");
99447
+ ctx.deps.stderr.write(`${heading(title, style)}
99448
+
99449
+ ${fields(rows, style).join(`
99450
+ `)}
99451
+ `);
99452
+ if (note)
99453
+ notice(ctx.deps, "stderr", "note", note);
99454
+ }
98195
99455
  async function confirmTransfer(ctx, lines, yes) {
98196
99456
  const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
98197
99457
  for (const line of lines)
@@ -98273,8 +99533,11 @@ async function transfer(args, ctx) {
98273
99533
  if (builtAmount === null || !isMax && builtAmount !== amountRaw)
98274
99534
  throw new TradingError("INVALID_RESPONSE", "The transfer build does not name the confirmed amount; nothing was signed.");
98275
99535
  const amountShown = asset ? `${decimalAmount(builtAmount, BASES[asset]?.decimals ?? 0)} ${asset}` : `${builtAmount} raw units of ${label}`;
98276
- ctx.deps.stderr.write(`Built transfer ${transferId}: ${amountShown}${typeof built.destinationKind === "string" ? ` to ${built.destinationKind === "vault" ? "the vault" : "a linked wallet"}` : ""}.
98277
- `);
99536
+ writePreview(ctx, "Built", [
99537
+ ["Transfer", transferId],
99538
+ ["Amount", amountShown],
99539
+ ...typeof built.destinationKind === "string" ? [["To", built.destinationKind === "vault" ? "the vault" : "a linked wallet"]] : []
99540
+ ]);
98278
99541
  const reader = await solana();
98279
99542
  const problem = await tradingRead(ctx, reader, () => solanaSpendProblem(unsigned[0], wallet3.address, reader.rpc, {
98280
99543
  kind: "transfer",
@@ -98284,8 +99547,12 @@ async function transfer(args, ctx) {
98284
99547
  }));
98285
99548
  if (problem)
98286
99549
  throw new TradingError("INVALID_RESPONSE", `The transaction Candle built does not move ${safeText(amountShown)} to ${destination.address} and nothing else (${safeText(problem)}); nothing was signed.`);
98287
- ctx.deps.stderr.write(`Checked locally: it moves ${safeText(amountShown)} from ${wallet3.address} to ${destination.address} and nothing else (simulated; a program can still behave differently once signed).
98288
- `);
99550
+ writePreview(ctx, "Checked locally", [
99551
+ ["Moves", safeText(amountShown)],
99552
+ ["From", wallet3.address],
99553
+ ["To", destination.address],
99554
+ ["Else", "nothing else"]
99555
+ ], "Simulated. A program can still behave differently once signed.");
98289
99556
  const signed2 = await relaySign(ctx, key, wallet3, unsigned[0]);
98290
99557
  const result = await request(ctx, key, "/api/v1/agent/transfer/submit", {
98291
99558
  transferId,
@@ -98364,8 +99631,15 @@ async function hoodTransfer(ctx, args) {
98364
99631
  throw new TradingError("INVALID_RESPONSE", `The leg Candle built does not move ${decimalAmount(amountRaw, decimals)} ${label} to ${destination.address}; nothing was signed.`);
98365
99632
  const leg = first.nextLeg;
98366
99633
  const maxFee = BigInt(leg.maxFeePerGas);
98367
- ctx.deps.stderr.write(`Built transfer ${safeText(transferId)}: ${decimalAmount(amountRaw, decimals)} ${safeText(label)}${typeof built.destinationKind === "string" ? ` to ${built.destinationKind === "vault" ? "the vault" : "a linked wallet"}` : ""}. Gas up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei).
98368
- `);
99634
+ writePreview(ctx, "Built", [
99635
+ ["Transfer", safeText(transferId)],
99636
+ ["Amount", `${decimalAmount(amountRaw, decimals)} ${safeText(label)}`],
99637
+ ...typeof built.destinationKind === "string" ? [["To", built.destinationKind === "vault" ? "the vault" : "a linked wallet"]] : [],
99638
+ [
99639
+ "Gas",
99640
+ `up to ${formatUnits(BigInt(leg.gas) * maxFee, 18)} ETH (gas ${leg.gas} at ${formatUnits(maxFee, 9)} gwei)`
99641
+ ]
99642
+ ]);
98369
99643
  const run2 = await runSequencedLegs(ctx, key, {
98370
99644
  wallet: wallet3,
98371
99645
  first,
@@ -100651,8 +101925,8 @@ async function addTouchIdFactor(ctx, parsed, resolvedVault, hold) {
100651
101925
  });
100652
101926
  }
100653
101927
  if (helper.biometry !== "available") {
100654
- const described = describeBiometry(helper);
100655
- throw new VaultError("VAULT_FACTOR_UNAVAILABLE", described.message, { suggestion: described.suggestion });
101928
+ const described2 = describeBiometry(helper);
101929
+ throw new VaultError("VAULT_FACTOR_UNAVAILABLE", described2.message, { suggestion: described2.suggestion });
100656
101930
  }
100657
101931
  const session = {
100658
101932
  path: helper.path,
@@ -105448,6 +106722,8 @@ async function resolveLegacyPath(env, home) {
105448
106722
 
105449
106723
  // src/commands/vault-status.ts
105450
106724
  init_args();
106725
+ init_presentation();
106726
+ init_render();
105451
106727
  init_fido2();
105452
106728
  init_format();
105453
106729
  init_passphrase();
@@ -105456,7 +106732,9 @@ init_sidecar();
105456
106732
  init_store();
105457
106733
  init_vault_support();
105458
106734
  var LIST_POINTER = "One line per key, with an optional SOL balance: candle vault list";
105459
- var NO_VERIFIED_BACKUP_NOTE = "No backup of this vault has ever been verified from this machine. If this file is lost, only the 24-word recovery phrase can rebuild it, and it rebuilds derived keys only. Take one now: candle vault backup --to <path> (on a Mac with iCloud Drive: candle vault backup --to icloud)";
106735
+ var NO_VERIFIED_BACKUP_NOTE = `No backup of this vault has ever been verified from this machine. If this file is lost, only the 24-word recovery phrase can rebuild it, and it rebuilds derived keys only. Take one now:
106736
+ candle vault backup --to <path>
106737
+ on a Mac with iCloud Drive: candle vault backup --to icloud`;
105460
106738
  async function vaultStatus(args, ctx) {
105461
106739
  const parsed = parseArgs(args, {
105462
106740
  valueFlags: ["--keystore"],
@@ -105535,132 +106813,119 @@ async function vaultStatus(args, ctx) {
105535
106813
  });
105536
106814
  return 0;
105537
106815
  }
105538
- deps.stdout.write(`${path}
105539
- `);
105540
- deps.stdout.write(` version ${file.version}
105541
- `);
105542
- deps.stdout.write(` generation ${file.generation}
105543
- `);
105544
- deps.stdout.write(` updated ${file.updatedAt}
105545
- `);
105546
- deps.stdout.write(`
105547
- Factors (${envelopes.length}), ${recoverable} recoverable (domains counted once):
105548
- `);
106816
+ const style = terminalStyle(deps, "stdout");
106817
+ const miss = symbols(style).missing;
106818
+ const write = (line = "") => deps.stdout.write(`${line}
106819
+ `);
106820
+ write(path);
106821
+ write("");
106822
+ write(heading("Vault", style));
106823
+ write("");
106824
+ for (const line of fields([
106825
+ ["Version", String(file.version)],
106826
+ ["Generation", String(file.generation)],
106827
+ ["Updated", formatTime(file.updatedAt)]
106828
+ ], style))
106829
+ write(line);
106830
+ write("");
106831
+ write(heading("Factors", style));
106832
+ write("");
106833
+ write(`${" ".repeat(2)}${envelopes.length} factors, ${recoverable} recoverable (domains counted once)`);
105549
106834
  for (const envelope of envelopes) {
105550
- deps.stdout.write(` ${envelope.id} ${envelope.factor}${envelope.transport ? `/${envelope.transport}` : ""} ${envelope.domain} ${envelope.availability}
105551
- `);
105552
- deps.stdout.write(` label ${envelope.label || "(none)"}
105553
- `);
105554
- if (envelope.strength)
105555
- deps.stdout.write(` strength ${envelope.strengthLabel}
105556
- `);
106835
+ write("");
106836
+ write(`${" ".repeat(2)}${envelope.id}`);
106837
+ const rows = [
106838
+ ["Factor", envelope.factor],
106839
+ ["Domain", envelope.domain],
106840
+ ["Availability", envelope.availability],
106841
+ ["Label", envelope.label || miss]
106842
+ ];
106843
+ if (envelope.transport)
106844
+ rows.splice(1, 0, ["Transport", envelope.transport]);
106845
+ if (envelope.strengthLabel)
106846
+ rows.push(["Strength", envelope.strengthLabel]);
105557
106847
  if (envelope.sharedDomainNote)
105558
- deps.stdout.write(` note ${envelope.sharedDomainNote}
105559
- `);
106848
+ rows.push(["Note", envelope.sharedDomainNote]);
106849
+ for (const line of fields(rows, style, 4))
106850
+ write(line);
105560
106851
  }
105561
106852
  if (recoverable === 1) {
105562
- deps.stdout.write(`
105563
- This vault has exactly one recoverable factor. Lose it and no copy of this file can be opened; the recovery phrase is then the only route, and it restores derived keys only.
105564
- `);
106853
+ notice(deps, "stdout", "warning", "This vault has exactly one recoverable factor. Lose it and no copy of this file can be opened; the recovery phrase is then the only route, and it restores derived keys only.");
105565
106854
  }
106855
+ write("");
106856
+ write(heading("This machine", style));
105566
106857
  if (sidecar) {
105567
- deps.stdout.write(`
105568
- This machine's record (vault.state.json, cleartext, best effort):
105569
- `);
105570
- deps.stdout.write(` last generation seen ${sidecar.lastGeneration}
105571
- `);
106858
+ write("");
106859
+ for (const line of fields([
106860
+ ["Record", "vault.state.json, cleartext, best effort"],
106861
+ ["Last generation seen", String(sidecar.lastGeneration)]
106862
+ ], style))
106863
+ write(line);
105572
106864
  if (sidecar.lastVerifiedBackupAt && backup) {
105573
106865
  for (const line of lastBackupLines(sidecar, backup))
105574
- deps.stdout.write(`${line}
105575
- `);
106866
+ write(line);
105576
106867
  }
105577
106868
  if (sidecar.lastBackupSharedDomainAccepted) {
105578
- deps.stdout.write(` shared domain accepted for the last backup destination (an unsealed copy, every envelope included)
105579
- `);
106869
+ write(" shared domain accepted for the last backup destination (an unsealed copy, every envelope included)");
105580
106870
  }
105581
106871
  if (sidecar.removedEnvelopeIds.length > 0) {
105582
- deps.stdout.write(` removed envelopes ${sidecar.removedEnvelopeIds.join(", ")}
105583
- `);
105584
- deps.stdout.write(` any copy of this file made while one existed still opens with that factor.
105585
- `);
106872
+ write(` removed envelopes ${sidecar.removedEnvelopeIds.join(", ")}`);
106873
+ write(" any copy of this file made while one existed still opens with that factor.");
105586
106874
  }
105587
106875
  } else {
105588
- deps.stdout.write(`
105589
- No vault.state.json beside this vault, so an older copy of it cannot be recognized on this machine.
105590
- `);
105591
- }
105592
- if (sidecar?.lastVerifiedBackupAt === undefined) {
105593
- deps.stdout.write(`
105594
- ${NO_VERIFIED_BACKUP_NOTE}
105595
- `);
106876
+ notice(deps, "stdout", "note", "No vault.state.json beside this vault, so an older copy of it cannot be recognized on this machine.");
105596
106877
  }
106878
+ if (sidecar?.lastVerifiedBackupAt === undefined)
106879
+ notice(deps, "stdout", "warning", NO_VERIFIED_BACKUP_NOTE);
105597
106880
  if (legacyPresent) {
105598
- deps.stdout.write(`
105599
- A legacy wallets.enc exists at ${legacy}; this CLI does not read it.
105600
- `);
105601
- deps.stdout.write(` A key it holds that was imported is already a linked wallet, managed with: candle wallets
105602
- `);
105603
- deps.stdout.write(` A key it holds that was never imported opens with an earlier release's \`wallets export\`; move those funds on chain to a vault key.
105604
- `);
106881
+ notice(deps, "stdout", "warning", `A legacy wallets.enc exists at ${legacy}; this CLI does not read it.
106882
+ A key it holds that was imported is already a linked wallet, managed with: candle wallets
106883
+ A key it holds that was never imported opens with an earlier release's \`wallets export\`; move those funds on chain to a vault key.`);
105605
106884
  }
105606
106885
  if (!unlocked) {
105607
- deps.stdout.write(`
105608
- Run with --unlock to list the keys, the derivation counters and the exposure flags, which are inside the encrypted index.
105609
- `);
106886
+ notice(deps, "stdout", "note", "Run with --unlock to list the keys, the derivation counters and the exposure flags, which are inside the encrypted index.");
105610
106887
  return 0;
105611
106888
  }
105612
- deps.stdout.write(`
105613
- Keys (${unlocked.entries.length}):
105614
- `);
106889
+ write("");
106890
+ write(heading(`Keys (${unlocked.entries.length})`, style));
105615
106891
  for (const entry of unlocked.entries) {
105616
- deps.stdout.write(` ${entry.address}
105617
- `);
105618
- deps.stdout.write(` label ${entry.label || "(none)"}
105619
- `);
105620
- deps.stdout.write(` role ${entry.role} (${entry.origin})
105621
- `);
106892
+ write("");
106893
+ write(`${" ".repeat(2)}${entry.address}`);
106894
+ const rows = [
106895
+ ["Label", entry.label || miss],
106896
+ ["Role", `${entry.role} (${entry.origin})`],
106897
+ ["Exposure", entry.exposure]
106898
+ ];
105622
106899
  if (entry.derivation)
105623
- deps.stdout.write(` derivation ${entry.derivation}
105624
- `);
105625
- deps.stdout.write(` exposure ${entry.exposure}
105626
- `);
106900
+ rows.splice(2, 0, ["Derivation", entry.derivation]);
105627
106901
  if (entry.teeLifecycle)
105628
- deps.stdout.write(` tee ${entry.teeLifecycle}${entry.teeRemoteState ? ` (server: ${entry.teeRemoteState})` : ""}
105629
- `);
106902
+ rows.push(["Tee", `${entry.teeLifecycle}${entry.teeRemoteState ? ` (server: ${entry.teeRemoteState})` : ""}`]);
105630
106903
  if (entry.destinationExposureAccepted) {
105631
- deps.stdout.write(` accepted an operator asserted this pinned destination's history despite exposureUnknown
105632
- `);
106904
+ rows.push(["Accepted", "an operator asserted this pinned destination's history despite exposureUnknown"]);
105633
106905
  }
106906
+ for (const line of fields(rows, style, 4))
106907
+ write(line);
105634
106908
  }
105635
- deps.stdout.write(`
105636
- ${LIST_POINTER}
105637
- `);
106909
+ write("");
106910
+ write(LIST_POINTER);
105638
106911
  if (unlocked.duplicateLabels.length > 0) {
105639
- deps.stdout.write(`
105640
- Duplicate labels (${unlocked.duplicateLabels.length}):
105641
- `);
106912
+ write("");
106913
+ write(heading(`Duplicate labels (${unlocked.duplicateLabels.length})`, style));
105642
106914
  for (const line of duplicateLabelLines(unlocked.duplicateLabels))
105643
- deps.stdout.write(`${line}
105644
- `);
106915
+ write(line);
105645
106916
  }
105646
- deps.stdout.write(`
105647
- Derivation counters (next index per branch):
105648
- `);
106917
+ write("");
106918
+ write(heading("Derivation counters (next index per branch)", style));
106919
+ write("");
105649
106920
  for (const [branch, value] of Object.entries(unlocked.nextIndex)) {
105650
106921
  const exposed = unlocked.exposedIndexes[branch] ?? [];
105651
- deps.stdout.write(` ${branch.padEnd(12)} ${value}${exposed.length > 0 ? ` known exposed: ${exposed.join(", ")}` : ""}
105652
- `);
106922
+ write(` ${branch.padEnd(12)} ${value}${exposed.length > 0 ? ` known exposed: ${exposed.join(", ")}` : ""}`);
105653
106923
  }
105654
- deps.stdout.write(` recovery phrase exported: ${unlocked.rootExported ? "yes" : "no"}
105655
- `);
106924
+ write(` recovery phrase exported: ${unlocked.rootExported ? "yes" : "no"}`);
105656
106925
  if (unlocked.restored) {
105657
- deps.stdout.write(`
105658
- This vault was built by \`vault restore --phrase\`. Its keys are recovery artifacts, not fresh cold keys, and it does not allocate new addresses; \`vault new-key\` and \`vault promote --from\` refuse here.
105659
- `);
106926
+ notice(deps, "stdout", "note", "This vault was built by `vault restore --phrase`.\nIts keys are recovery artifacts, not fresh cold keys, and it does not allocate new addresses; `vault new-key` and `vault promote --from` refuse here.");
105660
106927
  }
105661
- deps.stdout.write(`
105662
- The recovery phrase restores derived keys only. It does not restore any key imported from the Phase 1 TEE wallet store; the vault file plus a factor does that.
105663
- `);
106928
+ notice(deps, "stdout", "note", "The recovery phrase restores derived keys only. It does not restore any key imported from the Phase 1 TEE wallet store; the vault file plus a factor does that.");
105664
106929
  return 0;
105665
106930
  });
105666
106931
  }
@@ -105693,7 +106958,7 @@ function lastBackupLines(sidecar, backup) {
105693
106958
  return factors.length > 0 ? `, sealed: opens with the passphrase or ${factors.join(", ")}` : ", sealed: opens with the passphrase only";
105694
106959
  };
105695
106960
  const lines = [
105696
- ` last verified backup ${sidecar.lastVerifiedBackupAt} (${sidecar.lastBackupDomain ?? "not recorded"}${opensWith()})`
106961
+ ` last verified backup ${formatTime(sidecar.lastVerifiedBackupAt)} (${sidecar.lastBackupDomain ?? "not recorded"}${opensWith()})`
105697
106962
  ];
105698
106963
  if (backup.freshBackupAdvised.length > 0) {
105699
106964
  lines.push(` fresh backup advised the last ${sidecar.lastBackupSealed ? "sealed " : ""}copy does not carry security key ${backup.freshBackupAdvised.join(", ")}; a new \`candle vault backup\` to the same destination would open with it too.`);
@@ -106211,9 +107476,9 @@ function solOf(lamports) {
106211
107476
  const digits = lamports.padStart(10, "0");
106212
107477
  return `${digits.slice(0, -9)}.${digits.slice(-9)}`.replace(/\.?0+$/, "");
106213
107478
  }
106214
- function previewLines3(preview, walletName) {
107479
+ function previewLines3(preview, walletName2) {
106215
107480
  const lines = [
106216
- `Close ${preview.accountCount} empty token account${preview.accountCount === 1 ? "" : "s"} on the ${walletName}`,
107481
+ `Close ${preview.accountCount} empty token account${preview.accountCount === 1 ? "" : "s"} on the ${walletName2}`,
106217
107482
  `Rent returned to that same wallet: ${safeText(preview.totalSol)} SOL in ${preview.transactions} transaction${preview.transactions === 1 ? "" : "s"}, network fee about ${solOf(preview.estimatedFeeLamports)} SOL, net ${safeText(preview.netSol)} SOL`
106218
107483
  ];
106219
107484
  for (const row of preview.accounts.slice(0, LISTED_ROWS))
@@ -106242,12 +107507,12 @@ async function confirmClose(ctx, lines, yes) {
106242
107507
  throw new TradingError("CONFIRMATION_REQUIRED", "Run interactively to confirm, or use --yes to close without a prompt.");
106243
107508
  return (await ctx.deps.promptLine("Proceed? [y/N] ")).trim().toLowerCase() === "y";
106244
107509
  }
106245
- async function previewAndConfirm(ctx, preview, walletName, yes) {
107510
+ async function previewAndConfirm(ctx, preview, walletName2, yes) {
106246
107511
  if (preview.accounts.length === 0) {
106247
107512
  const output = ctx.json ? ctx.deps.stderr : ctx.deps.stdout;
106248
- output.write(`No empty token accounts to close on the ${walletName}.
107513
+ output.write(`No empty token accounts to close on the ${walletName2}.
106249
107514
  `);
106250
- for (const line of previewLines3(preview, walletName).slice(2))
107515
+ for (const line of previewLines3(preview, walletName2).slice(2))
106251
107516
  output.write(`${line}
106252
107517
  `);
106253
107518
  return printTradingResult(ctx, {
@@ -106257,7 +107522,7 @@ async function previewAndConfirm(ctx, preview, walletName, yes) {
106257
107522
  preview
106258
107523
  });
106259
107524
  }
106260
- if (!await confirmClose(ctx, previewLines3(preview, walletName), yes))
107525
+ if (!await confirmClose(ctx, previewLines3(preview, walletName2), yes))
106261
107526
  return printTradingResult(ctx, {
106262
107527
  success: true,
106263
107528
  status: "cancelled",
@@ -106457,17 +107722,17 @@ async function walletsCloseEmpty(args, ctx) {
106457
107722
  return 2;
106458
107723
  }
106459
107724
  const run2 = { id, named: named !== undefined, keep: lifted.keep, yes: parsed.booleans.has("--yes") };
106460
- const walletName = parsed.values["--wallet"];
107725
+ const walletName2 = parsed.values["--wallet"];
106461
107726
  try {
106462
107727
  const key = await tradingKey(ctx);
106463
- if (walletName === undefined || walletName === "embedded")
107728
+ if (walletName2 === undefined || walletName2 === "embedded")
106464
107729
  return await closeEmbedded(ctx, key, run2);
106465
- if (walletName === await embeddedAddress2(ctx, key))
107730
+ if (walletName2 === await embeddedAddress2(ctx, key))
106466
107731
  return await closeEmbedded(ctx, key, run2);
106467
- const payer = await tradingPayer(ctx, key, walletName, "swap:write", "solana");
107732
+ const payer = await tradingPayer(ctx, key, walletName2, "swap:write", "solana");
106468
107733
  if (payer.kind === "embedded")
106469
107734
  return await closeEmbedded(ctx, key, run2);
106470
- return await closeTee(ctx, key, payer.wallet, walletName, run2);
107735
+ return await closeTee(ctx, key, payer.wallet, walletName2, run2);
106471
107736
  } catch (error) {
106472
107737
  return tradingFailure(ctx, error);
106473
107738
  }