@spectratools/assembly-cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +47 -197
  2. package/dist/cli.js +953 -108
  3. package/package.json +19 -2
package/dist/cli.js CHANGED
@@ -29,6 +29,19 @@ function relTime(unixSeconds) {
29
29
  function asNum(value) {
30
30
  return Number(value);
31
31
  }
32
+ function jsonSafe(value) {
33
+ if (typeof value === "bigint") return value.toString();
34
+ if (Array.isArray(value)) return value.map((item) => jsonSafe(item));
35
+ if (value && typeof value === "object") {
36
+ return Object.fromEntries(
37
+ Object.entries(value).map(([key, entry]) => [
38
+ key,
39
+ jsonSafe(entry)
40
+ ])
41
+ );
42
+ }
43
+ return value;
44
+ }
32
45
 
33
46
  // src/commands/council.ts
34
47
  import { Cli, z } from "incur";
@@ -4268,6 +4281,10 @@ var ABSTRACT_MAINNET_ADDRESSES = {
4268
4281
  governance: "0xe82a25937e07a3855d8B8352b85fF4B4Aa3fb0C0",
4269
4282
  treasury: "0xC2e6DDbdc1A8e4DcCc60A78B6Faa197967a8FEb9"
4270
4283
  };
4284
+ var ABSTRACT_MAINNET_DEPLOYMENT_BLOCKS = {
4285
+ // https://abscan.org/tx/0xe1dd27a739944c9847a7f366ea2363e7bf0fc2067377020fd749a8301e09b1ec
4286
+ registry: 43782651n
4287
+ };
4271
4288
 
4272
4289
  // src/contracts/client.ts
4273
4290
  import { http, createPublicClient, defineChain } from "viem";
@@ -4293,12 +4310,40 @@ function createAssemblyPublicClient(rpcUrl) {
4293
4310
  }
4294
4311
 
4295
4312
  // src/commands/council.ts
4296
- var env = z.object({ ABSTRACT_RPC_URL: z.string().optional() });
4313
+ var env = z.object({
4314
+ ABSTRACT_RPC_URL: z.string().optional().describe("Abstract RPC URL override")
4315
+ });
4316
+ function decodeSeat(value) {
4317
+ const [owner, startAt, endAt, forfeited] = value;
4318
+ return { owner, startAt, endAt, forfeited };
4319
+ }
4320
+ function decodeAuction(value) {
4321
+ const [highestBidder, highestBid, settled] = value;
4322
+ return { highestBidder, highestBid, settled };
4323
+ }
4324
+ function deriveAuctionStatus(params) {
4325
+ if (params.settled) return "settled";
4326
+ if (params.currentTimestamp < params.windowEnd) return "bidding";
4327
+ return "closed";
4328
+ }
4297
4329
  var council = Cli.create("council", {
4298
- description: "Read council seat and auction state."
4330
+ description: "Inspect council seats, members, auctions, and seat parameters."
4299
4331
  });
4300
4332
  council.command("seats", {
4333
+ description: "List all council seats and their occupancy windows.",
4301
4334
  env,
4335
+ output: z.array(
4336
+ z.object({
4337
+ id: z.number(),
4338
+ owner: z.string(),
4339
+ startAt: z.number(),
4340
+ startAtRelative: z.string(),
4341
+ endAt: z.number(),
4342
+ endAtRelative: z.string(),
4343
+ forfeited: z.boolean()
4344
+ })
4345
+ ),
4346
+ examples: [{ description: "List all council seats" }],
4302
4347
  async run(c) {
4303
4348
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4304
4349
  const count = await client.readContract({
@@ -4307,7 +4352,7 @@ council.command("seats", {
4307
4352
  functionName: "seatCount"
4308
4353
  });
4309
4354
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i));
4310
- const seats = ids.length ? await client.multicall({
4355
+ const seatTuples = ids.length ? await client.multicall({
4311
4356
  allowFailure: false,
4312
4357
  contracts: ids.map((id) => ({
4313
4358
  abi: councilSeatsAbi,
@@ -4316,13 +4361,14 @@ council.command("seats", {
4316
4361
  args: [id]
4317
4362
  }))
4318
4363
  }) : [];
4364
+ const seats = seatTuples.map(decodeSeat);
4319
4365
  return c.ok(
4320
4366
  seats.map((seat, idx) => ({
4321
4367
  id: idx,
4322
4368
  owner: toChecksum(seat.owner),
4323
- startAt: Number(seat.startAt),
4369
+ startAt: asNum(seat.startAt),
4324
4370
  startAtRelative: relTime(seat.startAt),
4325
- endAt: Number(seat.endAt),
4371
+ endAt: asNum(seat.endAt),
4326
4372
  endAtRelative: relTime(seat.endAt),
4327
4373
  forfeited: seat.forfeited
4328
4374
  }))
@@ -4330,26 +4376,49 @@ council.command("seats", {
4330
4376
  }
4331
4377
  });
4332
4378
  council.command("seat", {
4333
- args: z.object({ id: z.coerce.number().int().nonnegative() }),
4379
+ description: "Get detailed seat information for a specific seat id.",
4380
+ args: z.object({
4381
+ id: z.coerce.number().int().nonnegative().describe("Seat id (0-indexed)")
4382
+ }),
4334
4383
  env,
4384
+ output: z.object({
4385
+ id: z.number(),
4386
+ owner: z.string(),
4387
+ startAt: z.number(),
4388
+ endAt: z.number(),
4389
+ forfeited: z.boolean(),
4390
+ endAtRelative: z.string()
4391
+ }),
4392
+ examples: [{ args: { id: 0 }, description: "Inspect seat #0" }],
4335
4393
  async run(c) {
4336
4394
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4337
- const seat = await client.readContract({
4395
+ const seatTuple = await client.readContract({
4338
4396
  abi: councilSeatsAbi,
4339
4397
  address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4340
4398
  functionName: "seats",
4341
4399
  args: [BigInt(c.args.id)]
4342
4400
  });
4401
+ const seat = decodeSeat(seatTuple);
4343
4402
  return c.ok({
4344
4403
  id: c.args.id,
4345
- ...seat,
4346
4404
  owner: toChecksum(seat.owner),
4405
+ startAt: asNum(seat.startAt),
4406
+ endAt: asNum(seat.endAt),
4407
+ forfeited: seat.forfeited,
4347
4408
  endAtRelative: relTime(seat.endAt)
4348
4409
  });
4349
4410
  }
4350
4411
  });
4351
4412
  council.command("members", {
4413
+ description: "List currently active council members and voting power.",
4352
4414
  env,
4415
+ output: z.array(
4416
+ z.object({
4417
+ address: z.string(),
4418
+ votingPower: z.number()
4419
+ })
4420
+ ),
4421
+ examples: [{ description: "List active council members" }],
4353
4422
  async run(c) {
4354
4423
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4355
4424
  const count = await client.readContract({
@@ -4358,7 +4427,7 @@ council.command("members", {
4358
4427
  functionName: "seatCount"
4359
4428
  });
4360
4429
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i));
4361
- const seats = ids.length ? await client.multicall({
4430
+ const seatTuples = ids.length ? await client.multicall({
4362
4431
  allowFailure: false,
4363
4432
  contracts: ids.map((id) => ({
4364
4433
  abi: councilSeatsAbi,
@@ -4367,9 +4436,10 @@ council.command("members", {
4367
4436
  args: [id]
4368
4437
  }))
4369
4438
  }) : [];
4439
+ const seats = seatTuples.map(decodeSeat);
4370
4440
  const activeOwners = [
4371
4441
  ...new Set(
4372
- seats.filter((x) => !x.forfeited && Number(x.endAt) > Math.floor(Date.now() / 1e3)).map((x) => x.owner)
4442
+ seats.filter((x) => !x.forfeited && asNum(x.endAt) > Math.floor(Date.now() / 1e3)).map((x) => x.owner)
4373
4443
  )
4374
4444
  ];
4375
4445
  const powers = activeOwners.length ? await client.multicall({
@@ -4390,8 +4460,21 @@ council.command("members", {
4390
4460
  }
4391
4461
  });
4392
4462
  council.command("is-member", {
4393
- args: z.object({ address: z.string() }),
4463
+ description: "Check whether an address is currently a council member.",
4464
+ args: z.object({
4465
+ address: z.string().describe("Address to check")
4466
+ }),
4394
4467
  env,
4468
+ output: z.object({
4469
+ address: z.string(),
4470
+ isMember: z.boolean()
4471
+ }),
4472
+ examples: [
4473
+ {
4474
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
4475
+ description: "Check council status for one address"
4476
+ }
4477
+ ],
4395
4478
  async run(c) {
4396
4479
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4397
4480
  const isMember = await client.readContract({
@@ -4404,8 +4487,21 @@ council.command("is-member", {
4404
4487
  }
4405
4488
  });
4406
4489
  council.command("voting-power", {
4407
- args: z.object({ address: z.string() }),
4490
+ description: "Get the current voting power for an address.",
4491
+ args: z.object({
4492
+ address: z.string().describe("Address to inspect")
4493
+ }),
4408
4494
  env,
4495
+ output: z.object({
4496
+ address: z.string(),
4497
+ votingPower: z.number()
4498
+ }),
4499
+ examples: [
4500
+ {
4501
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
4502
+ description: "Get voting power for one address"
4503
+ }
4504
+ ],
4409
4505
  async run(c) {
4410
4506
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4411
4507
  const votingPower = await client.readContract({
@@ -4418,7 +4514,25 @@ council.command("voting-power", {
4418
4514
  }
4419
4515
  });
4420
4516
  council.command("auctions", {
4517
+ description: "List recent and current council auction slots and leading bids.",
4421
4518
  env,
4519
+ output: z.object({
4520
+ currentDay: z.number(),
4521
+ currentSlot: z.number(),
4522
+ auctions: z.array(
4523
+ z.object({
4524
+ day: z.number(),
4525
+ slot: z.number(),
4526
+ highestBidder: z.string(),
4527
+ highestBid: z.string(),
4528
+ settled: z.boolean(),
4529
+ windowEnd: z.number(),
4530
+ windowEndRelative: z.string(),
4531
+ status: z.enum(["bidding", "closed", "settled"])
4532
+ })
4533
+ )
4534
+ }),
4535
+ examples: [{ description: "Inspect current and recent auction slots" }],
4422
4536
  async run(c) {
4423
4537
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4424
4538
  const [day, slot, slotsPerDay] = await Promise.all([
@@ -4443,25 +4557,50 @@ council.command("auctions", {
4443
4557
  if (d < 0) continue;
4444
4558
  for (let s = 0; s < Number(slotsPerDay); s++) recent.push({ day: BigInt(d), slot: s });
4445
4559
  }
4446
- const auctions = recent.length ? await client.multicall({
4447
- allowFailure: false,
4448
- contracts: recent.map((x) => ({
4449
- abi: councilSeatsAbi,
4450
- address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4451
- functionName: "auctions",
4452
- args: [x.day, x.slot]
4453
- }))
4454
- }) : [];
4560
+ const [auctionTuples, windowEnds, latestBlock] = await Promise.all([
4561
+ recent.length ? client.multicall({
4562
+ allowFailure: false,
4563
+ contracts: recent.map((x) => ({
4564
+ abi: councilSeatsAbi,
4565
+ address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4566
+ functionName: "auctions",
4567
+ args: [x.day, x.slot]
4568
+ }))
4569
+ }) : Promise.resolve([]),
4570
+ recent.length ? client.multicall({
4571
+ allowFailure: false,
4572
+ contracts: recent.map((x) => ({
4573
+ abi: councilSeatsAbi,
4574
+ address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4575
+ functionName: "auctionWindowEnd",
4576
+ args: [x.day, x.slot]
4577
+ }))
4578
+ }) : Promise.resolve([]),
4579
+ client.getBlock({ blockTag: "latest" })
4580
+ ]);
4581
+ const auctions = auctionTuples.map(decodeAuction);
4582
+ const currentTimestamp = latestBlock.timestamp;
4455
4583
  return c.ok(
4456
4584
  {
4457
4585
  currentDay: asNum(day),
4458
4586
  currentSlot: asNum(slot),
4459
- auctions: recent.map((x, i) => ({
4460
- ...x,
4461
- highestBidder: toChecksum(auctions[i].highestBidder),
4462
- highestBid: eth(auctions[i].highestBid),
4463
- settled: auctions[i].settled
4464
- }))
4587
+ auctions: recent.map((x, i) => {
4588
+ const windowEnd = windowEnds[i];
4589
+ return {
4590
+ day: Number(x.day),
4591
+ slot: x.slot,
4592
+ highestBidder: toChecksum(auctions[i].highestBidder),
4593
+ highestBid: eth(auctions[i].highestBid),
4594
+ settled: auctions[i].settled,
4595
+ windowEnd: asNum(windowEnd),
4596
+ windowEndRelative: relTime(windowEnd),
4597
+ status: deriveAuctionStatus({
4598
+ settled: auctions[i].settled,
4599
+ windowEnd,
4600
+ currentTimestamp
4601
+ })
4602
+ };
4603
+ })
4465
4604
  },
4466
4605
  {
4467
4606
  cta: {
@@ -4476,31 +4615,76 @@ council.command("auctions", {
4476
4615
  }
4477
4616
  });
4478
4617
  council.command("auction", {
4618
+ description: "Get one auction slot by day + slot.",
4479
4619
  args: z.object({
4480
- day: z.coerce.number().int().nonnegative(),
4481
- slot: z.coerce.number().int().nonnegative()
4620
+ day: z.coerce.number().int().nonnegative().describe("Auction day index"),
4621
+ slot: z.coerce.number().int().nonnegative().describe("Slot index within day")
4482
4622
  }),
4483
4623
  env,
4624
+ output: z.object({
4625
+ day: z.number(),
4626
+ slot: z.number(),
4627
+ highestBidder: z.string(),
4628
+ highestBid: z.string(),
4629
+ settled: z.boolean(),
4630
+ windowEnd: z.number(),
4631
+ windowEndRelative: z.string(),
4632
+ status: z.enum(["bidding", "closed", "settled"])
4633
+ }),
4634
+ examples: [{ args: { day: 0, slot: 0 }, description: "Inspect day 0, slot 0 auction" }],
4484
4635
  async run(c) {
4485
4636
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4486
- const auction = await client.readContract({
4487
- abi: councilSeatsAbi,
4488
- address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4489
- functionName: "auctions",
4490
- args: [BigInt(c.args.day), c.args.slot]
4491
- });
4637
+ const [auctionTuple, windowEnd, latestBlock] = await Promise.all([
4638
+ client.readContract({
4639
+ abi: councilSeatsAbi,
4640
+ address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4641
+ functionName: "auctions",
4642
+ args: [BigInt(c.args.day), c.args.slot]
4643
+ }),
4644
+ client.readContract({
4645
+ abi: councilSeatsAbi,
4646
+ address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
4647
+ functionName: "auctionWindowEnd",
4648
+ args: [BigInt(c.args.day), c.args.slot]
4649
+ }),
4650
+ client.getBlock({ blockTag: "latest" })
4651
+ ]);
4652
+ const auction = decodeAuction(auctionTuple);
4653
+ const windowEndTimestamp = windowEnd;
4654
+ const currentTimestamp = latestBlock.timestamp;
4492
4655
  return c.ok({
4493
4656
  day: c.args.day,
4494
4657
  slot: c.args.slot,
4495
4658
  highestBidder: toChecksum(auction.highestBidder),
4496
4659
  highestBid: eth(auction.highestBid),
4497
- settled: auction.settled
4660
+ settled: auction.settled,
4661
+ windowEnd: asNum(windowEndTimestamp),
4662
+ windowEndRelative: relTime(windowEndTimestamp),
4663
+ status: deriveAuctionStatus({
4664
+ settled: auction.settled,
4665
+ windowEnd: windowEndTimestamp,
4666
+ currentTimestamp
4667
+ })
4498
4668
  });
4499
4669
  }
4500
4670
  });
4501
4671
  council.command("pending-refund", {
4502
- args: z.object({ address: z.string() }),
4672
+ description: "Get pending refundable bid amount for an address.",
4673
+ args: z.object({
4674
+ address: z.string().describe("Bidder address")
4675
+ }),
4503
4676
  env,
4677
+ output: z.object({
4678
+ address: z.string(),
4679
+ pendingRefund: z.string(),
4680
+ pendingRefundWei: z.string()
4681
+ }),
4682
+ examples: [
4683
+ {
4684
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
4685
+ description: "Check pending refund for an address"
4686
+ }
4687
+ ],
4504
4688
  async run(c) {
4505
4689
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4506
4690
  const amount = await client.readContract({
@@ -4517,7 +4701,17 @@ council.command("pending-refund", {
4517
4701
  }
4518
4702
  });
4519
4703
  council.command("params", {
4704
+ description: "Read council seat term and auction scheduling parameters.",
4520
4705
  env,
4706
+ output: z.object({
4707
+ SEAT_TERM: z.number(),
4708
+ AUCTION_SLOT_DURATION: z.number(),
4709
+ AUCTION_SLOTS_PER_DAY: z.number(),
4710
+ auctionEpochStart: z.number(),
4711
+ auctionWindowStart: z.number(),
4712
+ auctionWindowEnd: z.number()
4713
+ }),
4714
+ examples: [{ description: "Inspect council seat + auction timing constants" }],
4521
4715
  async run(c) {
4522
4716
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4523
4717
  const [SEAT_TERM, AUCTION_SLOT_DURATION, AUCTION_SLOTS_PER_DAY, auctionEpochStart] = await Promise.all([
@@ -4569,10 +4763,78 @@ council.command("params", {
4569
4763
 
4570
4764
  // src/commands/forum.ts
4571
4765
  import { Cli as Cli2, z as z2 } from "incur";
4572
- var env2 = z2.object({ ABSTRACT_RPC_URL: z2.string().optional() });
4573
- var forum = Cli2.create("forum", { description: "Read forum threads/comments/petitions." });
4766
+ var env2 = z2.object({
4767
+ ABSTRACT_RPC_URL: z2.string().optional().describe("Abstract RPC URL override")
4768
+ });
4769
+ function decodeThread(value) {
4770
+ const [id, kind, author, createdAt, category, title, body, proposalId, petitionId] = value;
4771
+ return {
4772
+ id: asNum(id),
4773
+ kind: asNum(kind),
4774
+ author: toChecksum(author),
4775
+ createdAt: asNum(createdAt),
4776
+ category,
4777
+ title,
4778
+ body,
4779
+ proposalId: asNum(proposalId),
4780
+ petitionId: asNum(petitionId)
4781
+ };
4782
+ }
4783
+ function decodeComment(value) {
4784
+ const [id, threadId, parentId, author, createdAt, body] = value;
4785
+ return {
4786
+ id: asNum(id),
4787
+ threadId: asNum(threadId),
4788
+ parentId: asNum(parentId),
4789
+ author: toChecksum(author),
4790
+ createdAt: asNum(createdAt),
4791
+ body
4792
+ };
4793
+ }
4794
+ function decodePetition(value) {
4795
+ const [
4796
+ id,
4797
+ proposer,
4798
+ createdAt,
4799
+ category,
4800
+ title,
4801
+ body,
4802
+ signatures,
4803
+ promoted,
4804
+ threadId,
4805
+ proposalInput
4806
+ ] = value;
4807
+ return {
4808
+ id: asNum(id),
4809
+ proposer: toChecksum(proposer),
4810
+ createdAt: asNum(createdAt),
4811
+ category,
4812
+ title,
4813
+ body,
4814
+ signatures: asNum(signatures),
4815
+ promoted,
4816
+ threadId: asNum(threadId),
4817
+ proposalInput: jsonSafe(proposalInput)
4818
+ };
4819
+ }
4820
+ var forum = Cli2.create("forum", {
4821
+ description: "Browse Assembly forum threads, comments, and petitions."
4822
+ });
4574
4823
  forum.command("threads", {
4824
+ description: "List forum threads with author and creation metadata.",
4575
4825
  env: env2,
4826
+ output: z2.array(
4827
+ z2.object({
4828
+ id: z2.number(),
4829
+ kind: z2.number(),
4830
+ author: z2.string(),
4831
+ createdAt: z2.number(),
4832
+ createdAtRelative: z2.string(),
4833
+ category: z2.string().nullable().optional(),
4834
+ title: z2.string().nullable().optional()
4835
+ })
4836
+ ),
4837
+ examples: [{ description: "List all forum threads" }],
4576
4838
  async run(c) {
4577
4839
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4578
4840
  const count = await client.readContract({
@@ -4581,7 +4843,7 @@ forum.command("threads", {
4581
4843
  functionName: "threadCount"
4582
4844
  });
4583
4845
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i + 1));
4584
- const items = ids.length ? await client.multicall({
4846
+ const threadTuples = ids.length ? await client.multicall({
4585
4847
  allowFailure: false,
4586
4848
  contracts: ids.map((id) => ({
4587
4849
  abi: forumAbi,
@@ -4590,15 +4852,16 @@ forum.command("threads", {
4590
4852
  args: [id]
4591
4853
  }))
4592
4854
  }) : [];
4855
+ const items = threadTuples.map(decodeThread);
4593
4856
  return c.ok(
4594
4857
  items.map((x) => ({
4595
- id: asNum(x.id),
4596
- kind: asNum(x.kind),
4597
- author: toChecksum(x.author),
4598
- createdAt: asNum(x.createdAt),
4858
+ id: x.id,
4859
+ kind: x.kind,
4860
+ author: x.author,
4861
+ createdAt: x.createdAt,
4599
4862
  createdAtRelative: relTime(x.createdAt),
4600
- category: x.category,
4601
- title: x.title
4863
+ category: x.category ?? null,
4864
+ title: x.title ?? null
4602
4865
  })),
4603
4866
  {
4604
4867
  cta: {
@@ -4613,11 +4876,19 @@ forum.command("threads", {
4613
4876
  }
4614
4877
  });
4615
4878
  forum.command("thread", {
4616
- args: z2.object({ id: z2.coerce.number().int().positive() }),
4879
+ description: "Get one thread and all comments associated with it.",
4880
+ args: z2.object({
4881
+ id: z2.coerce.number().int().positive().describe("Thread id (1-indexed)")
4882
+ }),
4617
4883
  env: env2,
4884
+ output: z2.object({
4885
+ thread: z2.record(z2.string(), z2.unknown()),
4886
+ comments: z2.array(z2.record(z2.string(), z2.unknown()))
4887
+ }),
4888
+ examples: [{ args: { id: 1 }, description: "Fetch thread #1 and its comments" }],
4618
4889
  async run(c) {
4619
4890
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4620
- const [thread, commentCount] = await Promise.all([
4891
+ const [threadTuple, commentCount] = await Promise.all([
4621
4892
  client.readContract({
4622
4893
  abi: forumAbi,
4623
4894
  address: ABSTRACT_MAINNET_ADDRESSES.forum,
@@ -4630,8 +4901,9 @@ forum.command("thread", {
4630
4901
  functionName: "commentCount"
4631
4902
  })
4632
4903
  ]);
4904
+ const thread = decodeThread(threadTuple);
4633
4905
  const ids = Array.from({ length: Number(commentCount) }, (_, i) => BigInt(i + 1));
4634
- const comments = ids.length ? await client.multicall({
4906
+ const commentTuples = ids.length ? await client.multicall({
4635
4907
  allowFailure: false,
4636
4908
  contracts: ids.map((id) => ({
4637
4909
  abi: forumAbi,
@@ -4640,15 +4912,21 @@ forum.command("thread", {
4640
4912
  args: [id]
4641
4913
  }))
4642
4914
  }) : [];
4915
+ const comments = commentTuples.map(decodeComment);
4643
4916
  return c.ok({
4644
- thread,
4645
- comments: comments.filter((x) => Number(x.threadId) === c.args.id)
4917
+ thread: jsonSafe(thread),
4918
+ comments: comments.filter((x) => x.threadId === c.args.id).map((comment) => jsonSafe(comment))
4646
4919
  });
4647
4920
  }
4648
4921
  });
4649
4922
  forum.command("comments", {
4650
- args: z2.object({ threadId: z2.coerce.number().int().positive() }),
4923
+ description: "List comments for a thread id.",
4924
+ args: z2.object({
4925
+ threadId: z2.coerce.number().int().positive().describe("Thread id to filter comments by")
4926
+ }),
4651
4927
  env: env2,
4928
+ output: z2.array(z2.record(z2.string(), z2.unknown())),
4929
+ examples: [{ args: { threadId: 1 }, description: "List comments for thread #1" }],
4652
4930
  async run(c) {
4653
4931
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4654
4932
  const count = await client.readContract({
@@ -4657,7 +4935,7 @@ forum.command("comments", {
4657
4935
  functionName: "commentCount"
4658
4936
  });
4659
4937
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i + 1));
4660
- const comments = ids.length ? await client.multicall({
4938
+ const commentTuples = ids.length ? await client.multicall({
4661
4939
  allowFailure: false,
4662
4940
  contracts: ids.map((id) => ({
4663
4941
  abi: forumAbi,
@@ -4666,25 +4944,36 @@ forum.command("comments", {
4666
4944
  args: [id]
4667
4945
  }))
4668
4946
  }) : [];
4669
- return c.ok(comments.filter((x) => Number(x.threadId) === c.args.threadId));
4947
+ const comments = commentTuples.map(decodeComment);
4948
+ return c.ok(
4949
+ comments.filter((x) => x.threadId === c.args.threadId).map((comment) => jsonSafe(comment))
4950
+ );
4670
4951
  }
4671
4952
  });
4672
4953
  forum.command("comment", {
4673
- args: z2.object({ id: z2.coerce.number().int().positive() }),
4954
+ description: "Get one comment by comment id.",
4955
+ args: z2.object({
4956
+ id: z2.coerce.number().int().positive().describe("Comment id (1-indexed)")
4957
+ }),
4674
4958
  env: env2,
4959
+ output: z2.record(z2.string(), z2.unknown()),
4960
+ examples: [{ args: { id: 1 }, description: "Fetch comment #1" }],
4675
4961
  async run(c) {
4676
4962
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4677
- const comment = await client.readContract({
4963
+ const commentTuple = await client.readContract({
4678
4964
  abi: forumAbi,
4679
4965
  address: ABSTRACT_MAINNET_ADDRESSES.forum,
4680
4966
  functionName: "comments",
4681
4967
  args: [BigInt(c.args.id)]
4682
4968
  });
4683
- return c.ok(comment);
4969
+ return c.ok(jsonSafe(decodeComment(commentTuple)));
4684
4970
  }
4685
4971
  });
4686
4972
  forum.command("petitions", {
4973
+ description: "List petitions submitted in the forum contract.",
4687
4974
  env: env2,
4975
+ output: z2.array(z2.record(z2.string(), z2.unknown())),
4976
+ examples: [{ description: "List all petitions" }],
4688
4977
  async run(c) {
4689
4978
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4690
4979
  const count = await client.readContract({
@@ -4693,7 +4982,7 @@ forum.command("petitions", {
4693
4982
  functionName: "petitionCount"
4694
4983
  });
4695
4984
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i + 1));
4696
- const petitions = ids.length ? await client.multicall({
4985
+ const petitionTuples = ids.length ? await client.multicall({
4697
4986
  allowFailure: false,
4698
4987
  contracts: ids.map((id) => ({
4699
4988
  abi: forumAbi,
@@ -4702,32 +4991,58 @@ forum.command("petitions", {
4702
4991
  args: [id]
4703
4992
  }))
4704
4993
  }) : [];
4705
- return c.ok(petitions);
4994
+ const petitions = petitionTuples.map(decodePetition);
4995
+ return c.ok(petitions.map((petition) => jsonSafe(petition)));
4706
4996
  }
4707
4997
  });
4708
4998
  forum.command("petition", {
4709
- args: z2.object({ id: z2.coerce.number().int().positive() }),
4999
+ description: "Get one petition plus whether proposer already signed it.",
5000
+ args: z2.object({
5001
+ id: z2.coerce.number().int().positive().describe("Petition id (1-indexed)")
5002
+ }),
4710
5003
  env: env2,
5004
+ output: z2.object({ proposerSigned: z2.boolean() }).passthrough(),
5005
+ examples: [{ args: { id: 1 }, description: "Fetch petition #1" }],
4711
5006
  async run(c) {
4712
5007
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4713
- const petition = await client.readContract({
4714
- abi: forumAbi,
4715
- address: ABSTRACT_MAINNET_ADDRESSES.forum,
4716
- functionName: "petitions",
4717
- args: [BigInt(c.args.id)]
4718
- });
5008
+ const petition = decodePetition(
5009
+ await client.readContract({
5010
+ abi: forumAbi,
5011
+ address: ABSTRACT_MAINNET_ADDRESSES.forum,
5012
+ functionName: "petitions",
5013
+ args: [BigInt(c.args.id)]
5014
+ })
5015
+ );
4719
5016
  const proposerSigned = await client.readContract({
4720
5017
  abi: forumAbi,
4721
5018
  address: ABSTRACT_MAINNET_ADDRESSES.forum,
4722
5019
  functionName: "hasSignedPetition",
4723
5020
  args: [BigInt(c.args.id), petition.proposer]
4724
5021
  });
4725
- return c.ok({ ...petition, proposerSigned });
5022
+ return c.ok({ ...jsonSafe(petition), proposerSigned });
4726
5023
  }
4727
5024
  });
4728
5025
  forum.command("has-signed", {
4729
- args: z2.object({ petitionId: z2.coerce.number().int().positive(), address: z2.string() }),
5026
+ description: "Check whether an address signed a petition.",
5027
+ args: z2.object({
5028
+ petitionId: z2.coerce.number().int().positive().describe("Petition id (1-indexed)"),
5029
+ address: z2.string().describe("Signer address to check")
5030
+ }),
4730
5031
  env: env2,
5032
+ output: z2.object({
5033
+ petitionId: z2.number(),
5034
+ address: z2.string(),
5035
+ hasSigned: z2.boolean()
5036
+ }),
5037
+ examples: [
5038
+ {
5039
+ args: {
5040
+ petitionId: 1,
5041
+ address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
5042
+ },
5043
+ description: "Check if an address signed petition #1"
5044
+ }
5045
+ ],
4731
5046
  async run(c) {
4732
5047
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4733
5048
  const hasSigned = await client.readContract({
@@ -4740,7 +5055,15 @@ forum.command("has-signed", {
4740
5055
  }
4741
5056
  });
4742
5057
  forum.command("stats", {
5058
+ description: "Read top-level forum counters and petition threshold.",
4743
5059
  env: env2,
5060
+ output: z2.object({
5061
+ threadCount: z2.number(),
5062
+ commentCount: z2.number(),
5063
+ petitionCount: z2.number(),
5064
+ petitionThresholdBps: z2.number()
5065
+ }),
5066
+ examples: [{ description: "Get forum counts and petition threshold" }],
4744
5067
  async run(c) {
4745
5068
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4746
5069
  const [threadCount, commentCount, petitionCount, petitionThresholdBps] = await Promise.all([
@@ -4776,12 +5099,105 @@ forum.command("stats", {
4776
5099
 
4777
5100
  // src/commands/governance.ts
4778
5101
  import { Cli as Cli3, z as z3 } from "incur";
4779
- var env3 = z3.object({ ABSTRACT_RPC_URL: z3.string().optional() });
5102
+ var env3 = z3.object({
5103
+ ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
5104
+ });
5105
+ function decodeProposal(value) {
5106
+ const [
5107
+ kind,
5108
+ configRiskTier,
5109
+ origin,
5110
+ status,
5111
+ proposer,
5112
+ threadId,
5113
+ petitionId,
5114
+ createdAt,
5115
+ deliberationEndsAt,
5116
+ voteStartAt,
5117
+ voteEndAt,
5118
+ timelockEndsAt,
5119
+ activeSeatsSnapshot,
5120
+ forVotes,
5121
+ againstVotes,
5122
+ abstainVotes,
5123
+ amount,
5124
+ snapshotAssetBalance,
5125
+ transferIntent,
5126
+ intentDeadline,
5127
+ intentMaxRiskTier,
5128
+ title,
5129
+ description
5130
+ ] = value;
5131
+ return {
5132
+ kind,
5133
+ configRiskTier,
5134
+ origin,
5135
+ status,
5136
+ proposer: toChecksum(proposer),
5137
+ threadId,
5138
+ petitionId,
5139
+ createdAt,
5140
+ deliberationEndsAt,
5141
+ voteStartAt,
5142
+ voteEndAt,
5143
+ timelockEndsAt,
5144
+ activeSeatsSnapshot,
5145
+ forVotes,
5146
+ againstVotes,
5147
+ abstainVotes,
5148
+ amount,
5149
+ snapshotAssetBalance,
5150
+ transferIntent,
5151
+ intentDeadline,
5152
+ intentMaxRiskTier,
5153
+ title,
5154
+ description
5155
+ };
5156
+ }
5157
+ function serializeProposal(proposal) {
5158
+ return {
5159
+ kind: asNum(proposal.kind),
5160
+ configRiskTier: asNum(proposal.configRiskTier),
5161
+ origin: asNum(proposal.origin),
5162
+ status: asNum(proposal.status),
5163
+ proposer: proposal.proposer,
5164
+ threadId: asNum(proposal.threadId),
5165
+ petitionId: asNum(proposal.petitionId),
5166
+ createdAt: asNum(proposal.createdAt),
5167
+ deliberationEndsAt: asNum(proposal.deliberationEndsAt),
5168
+ voteStartAt: asNum(proposal.voteStartAt),
5169
+ voteEndAt: asNum(proposal.voteEndAt),
5170
+ timelockEndsAt: asNum(proposal.timelockEndsAt),
5171
+ activeSeatsSnapshot: asNum(proposal.activeSeatsSnapshot),
5172
+ forVotes: proposal.forVotes.toString(),
5173
+ againstVotes: proposal.againstVotes.toString(),
5174
+ abstainVotes: proposal.abstainVotes.toString(),
5175
+ amount: proposal.amount.toString(),
5176
+ snapshotAssetBalance: proposal.snapshotAssetBalance.toString(),
5177
+ transferIntent: proposal.transferIntent,
5178
+ intentDeadline: asNum(proposal.intentDeadline),
5179
+ intentMaxRiskTier: asNum(proposal.intentMaxRiskTier),
5180
+ title: proposal.title,
5181
+ description: proposal.description
5182
+ };
5183
+ }
4780
5184
  var governance = Cli3.create("governance", {
4781
- description: "Read governance proposals and params."
5185
+ description: "Inspect Assembly governance proposals, votes, and parameters."
4782
5186
  });
4783
5187
  governance.command("proposals", {
5188
+ description: "List governance proposals with status and vote end time.",
4784
5189
  env: env3,
5190
+ output: z3.array(
5191
+ z3.object({
5192
+ id: z3.number(),
5193
+ kind: z3.number(),
5194
+ status: z3.number(),
5195
+ title: z3.string().nullable().optional(),
5196
+ voteEndAt: z3.number(),
5197
+ voteEndRelative: z3.string()
5198
+ })
5199
+ ),
5200
+ examples: [{ description: "List all proposals" }],
4785
5201
  async run(c) {
4786
5202
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4787
5203
  const count = await client.readContract({
@@ -4790,7 +5206,7 @@ governance.command("proposals", {
4790
5206
  functionName: "proposalCount"
4791
5207
  });
4792
5208
  const ids = Array.from({ length: Number(count) }, (_, i) => BigInt(i + 1));
4793
- const proposals = ids.length ? await client.multicall({
5209
+ const proposalTuples = ids.length ? await client.multicall({
4794
5210
  allowFailure: false,
4795
5211
  contracts: ids.map((id) => ({
4796
5212
  abi: governanceAbi,
@@ -4799,12 +5215,13 @@ governance.command("proposals", {
4799
5215
  args: [id]
4800
5216
  }))
4801
5217
  }) : [];
5218
+ const proposals = proposalTuples.map(decodeProposal);
4802
5219
  return c.ok(
4803
5220
  proposals.map((p, i) => ({
4804
5221
  id: i + 1,
4805
5222
  kind: asNum(p.kind),
4806
5223
  status: asNum(p.status),
4807
- title: p.title,
5224
+ title: p.title ?? null,
4808
5225
  voteEndAt: asNum(p.voteEndAt),
4809
5226
  voteEndRelative: relTime(p.voteEndAt)
4810
5227
  })),
@@ -4821,22 +5238,47 @@ governance.command("proposals", {
4821
5238
  }
4822
5239
  });
4823
5240
  governance.command("proposal", {
4824
- args: z3.object({ id: z3.coerce.number().int().positive() }),
5241
+ description: "Get full raw proposal details by proposal id.",
5242
+ args: z3.object({
5243
+ id: z3.coerce.number().int().positive().describe("Proposal id (1-indexed)")
5244
+ }),
4825
5245
  env: env3,
5246
+ output: z3.record(z3.string(), z3.unknown()),
5247
+ examples: [{ args: { id: 1 }, description: "Fetch proposal #1" }],
4826
5248
  async run(c) {
4827
5249
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4828
- const proposal = await client.readContract({
4829
- abi: governanceAbi,
4830
- address: ABSTRACT_MAINNET_ADDRESSES.governance,
4831
- functionName: "proposals",
4832
- args: [BigInt(c.args.id)]
4833
- });
4834
- return c.ok(proposal);
5250
+ const proposal = decodeProposal(
5251
+ await client.readContract({
5252
+ abi: governanceAbi,
5253
+ address: ABSTRACT_MAINNET_ADDRESSES.governance,
5254
+ functionName: "proposals",
5255
+ args: [BigInt(c.args.id)]
5256
+ })
5257
+ );
5258
+ return c.ok(serializeProposal(proposal));
4835
5259
  }
4836
5260
  });
4837
5261
  governance.command("has-voted", {
4838
- args: z3.object({ proposalId: z3.coerce.number().int().positive(), address: z3.string() }),
5262
+ description: "Check if an address has voted on a proposal.",
5263
+ args: z3.object({
5264
+ proposalId: z3.coerce.number().int().positive().describe("Proposal id (1-indexed)"),
5265
+ address: z3.string().describe("Voter address")
5266
+ }),
4839
5267
  env: env3,
5268
+ output: z3.object({
5269
+ proposalId: z3.number(),
5270
+ address: z3.string(),
5271
+ hasVoted: z3.boolean()
5272
+ }),
5273
+ examples: [
5274
+ {
5275
+ args: {
5276
+ proposalId: 1,
5277
+ address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
5278
+ },
5279
+ description: "Check whether an address already voted"
5280
+ }
5281
+ ],
4840
5282
  async run(c) {
4841
5283
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4842
5284
  const hasVoted = await client.readContract({
@@ -4845,11 +5287,31 @@ governance.command("has-voted", {
4845
5287
  functionName: "hasVoted",
4846
5288
  args: [BigInt(c.args.proposalId), c.args.address]
4847
5289
  });
4848
- return c.ok({ proposalId: c.args.proposalId, address: c.args.address, hasVoted });
5290
+ return c.ok({
5291
+ proposalId: c.args.proposalId,
5292
+ address: toChecksum(c.args.address),
5293
+ hasVoted
5294
+ });
4849
5295
  }
4850
5296
  });
4851
5297
  governance.command("params", {
5298
+ description: "Read governance threshold and timing parameters.",
4852
5299
  env: env3,
5300
+ output: z3.object({
5301
+ deliberationPeriod: z3.number(),
5302
+ votePeriod: z3.number(),
5303
+ quorumBps: z3.number(),
5304
+ constitutionalDeliberationPeriod: z3.number(),
5305
+ constitutionalVotePeriod: z3.number(),
5306
+ constitutionalPassBps: z3.number(),
5307
+ majorPassBps: z3.number(),
5308
+ parameterPassBps: z3.number(),
5309
+ significantPassBps: z3.number(),
5310
+ significantThresholdBps: z3.number(),
5311
+ routineThresholdBps: z3.number(),
5312
+ timelockPeriod: z3.number()
5313
+ }),
5314
+ examples: [{ description: "Inspect governance timing and pass thresholds" }],
4853
5315
  async run(c) {
4854
5316
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4855
5317
  const getters = [
@@ -4874,33 +5336,191 @@ governance.command("params", {
4874
5336
  functionName: name
4875
5337
  }))
4876
5338
  });
4877
- return c.ok(Object.fromEntries(getters.map((k, i) => [k, asNum(values[i])])));
5339
+ return c.ok({
5340
+ deliberationPeriod: asNum(values[0]),
5341
+ votePeriod: asNum(values[1]),
5342
+ quorumBps: asNum(values[2]),
5343
+ constitutionalDeliberationPeriod: asNum(values[3]),
5344
+ constitutionalVotePeriod: asNum(values[4]),
5345
+ constitutionalPassBps: asNum(values[5]),
5346
+ majorPassBps: asNum(values[6]),
5347
+ parameterPassBps: asNum(values[7]),
5348
+ significantPassBps: asNum(values[8]),
5349
+ significantThresholdBps: asNum(values[9]),
5350
+ routineThresholdBps: asNum(values[10]),
5351
+ timelockPeriod: asNum(values[11])
5352
+ });
4878
5353
  }
4879
5354
  });
4880
5355
 
4881
5356
  // src/commands/members.ts
4882
5357
  import { Cli as Cli4, z as z4 } from "incur";
5358
+ var DEFAULT_MEMBER_SNAPSHOT_URL = "https://www.theaiassembly.org/api/indexer/members";
5359
+ var REGISTERED_EVENT_SCAN_STEP = 100000n;
5360
+ var REGISTERED_EVENT_SCAN_TIMEOUT_MS = 2e4;
4883
5361
  var env4 = z4.object({
4884
- ABSTRACT_RPC_URL: z4.string().optional(),
4885
- ASSEMBLY_INDEXER_URL: z4.string().optional()
5362
+ ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override"),
5363
+ ASSEMBLY_INDEXER_URL: z4.string().optional().describe("Optional members snapshot endpoint (default: theaiassembly.org indexer)")
4886
5364
  });
5365
+ var memberSnapshotSchema = z4.array(z4.string());
5366
+ var AssemblyApiValidationError = class extends Error {
5367
+ constructor(details) {
5368
+ super("Assembly API response validation failed");
5369
+ this.details = details;
5370
+ this.name = "AssemblyApiValidationError";
5371
+ }
5372
+ };
5373
+ var AssemblyIndexerUnavailableError = class extends Error {
5374
+ constructor(details) {
5375
+ super("Assembly indexer unavailable");
5376
+ this.details = details;
5377
+ this.name = "AssemblyIndexerUnavailableError";
5378
+ }
5379
+ };
4887
5380
  async function memberSnapshot(url) {
4888
- const res = await fetch(url);
4889
- if (!res.ok) return [];
5381
+ let res;
5382
+ try {
5383
+ res = await fetch(url);
5384
+ } catch (error) {
5385
+ throw new AssemblyIndexerUnavailableError({
5386
+ code: "ASSEMBLY_INDEXER_UNAVAILABLE",
5387
+ url,
5388
+ reason: error instanceof Error ? error.message : String(error)
5389
+ });
5390
+ }
5391
+ if (!res.ok) {
5392
+ throw new AssemblyIndexerUnavailableError({
5393
+ code: "ASSEMBLY_INDEXER_UNAVAILABLE",
5394
+ url,
5395
+ status: res.status,
5396
+ statusText: res.statusText
5397
+ });
5398
+ }
4890
5399
  const json = await res.json();
4891
- if (!Array.isArray(json)) return [];
4892
- return json.filter((x) => typeof x === "string");
5400
+ const parsed = memberSnapshotSchema.safeParse(json);
5401
+ if (parsed.success) {
5402
+ return parsed.data;
5403
+ }
5404
+ throw new AssemblyApiValidationError({
5405
+ code: "INVALID_ASSEMBLY_API_RESPONSE",
5406
+ url,
5407
+ issues: parsed.error.issues,
5408
+ response: json
5409
+ });
5410
+ }
5411
+ async function withTimeout(promise, timeoutMs, timeoutMessage) {
5412
+ let timer;
5413
+ try {
5414
+ return await Promise.race([
5415
+ promise,
5416
+ new Promise((_, reject) => {
5417
+ timer = setTimeout(() => {
5418
+ reject(new Error(timeoutMessage));
5419
+ }, timeoutMs);
5420
+ })
5421
+ ]);
5422
+ } finally {
5423
+ if (timer) clearTimeout(timer);
5424
+ }
5425
+ }
5426
+ async function membersFromRegisteredEvents(client) {
5427
+ const latestBlock = await client.getBlockNumber();
5428
+ const addresses = /* @__PURE__ */ new Set();
5429
+ for (let fromBlock = ABSTRACT_MAINNET_DEPLOYMENT_BLOCKS.registry; fromBlock <= latestBlock; fromBlock += REGISTERED_EVENT_SCAN_STEP) {
5430
+ const toBlock = fromBlock + REGISTERED_EVENT_SCAN_STEP - 1n > latestBlock ? latestBlock : fromBlock + REGISTERED_EVENT_SCAN_STEP - 1n;
5431
+ const events = await client.getContractEvents({
5432
+ abi: registryAbi,
5433
+ address: ABSTRACT_MAINNET_ADDRESSES.registry,
5434
+ eventName: "Registered",
5435
+ fromBlock,
5436
+ toBlock,
5437
+ strict: true
5438
+ });
5439
+ for (const event of events) {
5440
+ const member = event.args.member;
5441
+ if (typeof member === "string") {
5442
+ addresses.add(member);
5443
+ }
5444
+ }
5445
+ }
5446
+ return [...addresses];
5447
+ }
5448
+ function indexerIssue(details) {
5449
+ if (typeof details.status === "number") {
5450
+ return `${details.status}${details.statusText ? ` ${details.statusText}` : ""}`;
5451
+ }
5452
+ if (details.reason) return details.reason;
5453
+ return "unknown error";
5454
+ }
5455
+ function emitIndexerFallbackWarning(details) {
5456
+ process.stderr.write(
5457
+ `${JSON.stringify({
5458
+ level: "warn",
5459
+ code: details.code,
5460
+ message: "Member snapshot indexer is unavailable. Falling back to on-chain Registered events.",
5461
+ url: details.url,
5462
+ issue: indexerIssue(details)
5463
+ })}
5464
+ `
5465
+ );
4893
5466
  }
4894
5467
  var members = Cli4.create("members", {
4895
- description: "Read Assembly membership state from Registry."
5468
+ description: "Inspect Assembly membership and registry fee state."
4896
5469
  });
4897
5470
  members.command("list", {
4898
- description: "List members from indexer snapshot + onchain status.",
5471
+ description: "List members from an indexer snapshot (or Registered event fallback) plus on-chain active state.",
4899
5472
  env: env4,
5473
+ output: z4.array(
5474
+ z4.object({
5475
+ address: z4.string(),
5476
+ active: z4.boolean(),
5477
+ registered: z4.boolean(),
5478
+ activeUntil: z4.number(),
5479
+ activeUntilRelative: z4.string(),
5480
+ lastHeartbeatAt: z4.number(),
5481
+ lastHeartbeatRelative: z4.string()
5482
+ })
5483
+ ),
5484
+ examples: [
5485
+ { description: "List members using default indexer snapshot" },
5486
+ { description: "Override ASSEMBLY_INDEXER_URL to use a custom snapshot source" }
5487
+ ],
4900
5488
  async run(c) {
4901
5489
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4902
- const snapshotUrl = c.env.ASSEMBLY_INDEXER_URL ?? "https://www.theaiassembly.org/api/indexer/members";
4903
- const addresses = await memberSnapshot(snapshotUrl);
5490
+ const snapshotUrl = c.env.ASSEMBLY_INDEXER_URL ?? DEFAULT_MEMBER_SNAPSHOT_URL;
5491
+ let addresses;
5492
+ let fallbackReason;
5493
+ try {
5494
+ addresses = await memberSnapshot(snapshotUrl);
5495
+ } catch (error) {
5496
+ if (error instanceof AssemblyApiValidationError) {
5497
+ return c.error({
5498
+ code: error.details.code,
5499
+ message: `Member snapshot response failed validation. url=${error.details.url}; issues=${JSON.stringify(error.details.issues)}; response=${JSON.stringify(error.details.response)}`,
5500
+ retryable: false
5501
+ });
5502
+ }
5503
+ if (!(error instanceof AssemblyIndexerUnavailableError)) {
5504
+ throw error;
5505
+ }
5506
+ fallbackReason = error.details;
5507
+ try {
5508
+ addresses = await withTimeout(
5509
+ membersFromRegisteredEvents(client),
5510
+ REGISTERED_EVENT_SCAN_TIMEOUT_MS,
5511
+ `Registered event fallback scan timed out after ${REGISTERED_EVENT_SCAN_TIMEOUT_MS}ms`
5512
+ );
5513
+ } catch (fallbackError) {
5514
+ return c.error({
5515
+ code: "MEMBER_LIST_SOURCE_UNAVAILABLE",
5516
+ message: `Member indexer unavailable (${indexerIssue(error.details)} at ${error.details.url}) and on-chain Registered event fallback failed: ${fallbackError instanceof Error ? fallbackError.message : String(fallbackError)}`,
5517
+ retryable: true
5518
+ });
5519
+ }
5520
+ }
5521
+ if (fallbackReason) {
5522
+ emitIndexerFallbackWarning(fallbackReason);
5523
+ }
4904
5524
  const calls = addresses.flatMap((address) => [
4905
5525
  {
4906
5526
  abi: registryAbi,
@@ -4931,16 +5551,32 @@ members.command("list", {
4931
5551
  });
4932
5552
  return c.ok(rows, {
4933
5553
  cta: {
4934
- description: "Inspect one member:",
5554
+ description: fallbackReason ? `Indexer unavailable (${indexerIssue(fallbackReason)}); using on-chain Registered event fallback. Inspect one member:` : "Inspect one member:",
4935
5555
  commands: [{ command: "members info", args: { address: "<addr>" } }]
4936
5556
  }
4937
5557
  });
4938
5558
  }
4939
5559
  });
4940
5560
  members.command("info", {
4941
- description: "Read member info for an address.",
4942
- args: z4.object({ address: z4.string() }),
5561
+ description: "Get registry record and active status for a member address.",
5562
+ args: z4.object({
5563
+ address: z4.string().describe("Member wallet address")
5564
+ }),
4943
5565
  env: env4,
5566
+ output: z4.object({
5567
+ address: z4.string(),
5568
+ active: z4.boolean(),
5569
+ activeUntil: z4.number(),
5570
+ lastHeartbeatAt: z4.number(),
5571
+ activeUntilRelative: z4.string(),
5572
+ lastHeartbeatRelative: z4.string()
5573
+ }),
5574
+ examples: [
5575
+ {
5576
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
5577
+ description: "Inspect one member address"
5578
+ }
5579
+ ],
4944
5580
  async run(c) {
4945
5581
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4946
5582
  const [member, active] = await Promise.all([
@@ -4960,15 +5596,21 @@ members.command("info", {
4960
5596
  return c.ok({
4961
5597
  address: toChecksum(c.args.address),
4962
5598
  active,
4963
- ...member,
5599
+ activeUntil: Number(member.activeUntil),
5600
+ lastHeartbeatAt: Number(member.lastHeartbeatAt),
4964
5601
  activeUntilRelative: relTime(member.activeUntil),
4965
5602
  lastHeartbeatRelative: relTime(member.lastHeartbeatAt)
4966
5603
  });
4967
5604
  }
4968
5605
  });
4969
5606
  members.command("count", {
4970
- description: "Read active + known member counts.",
5607
+ description: "Get active and total-known member counts from Registry.",
4971
5608
  env: env4,
5609
+ output: z4.object({
5610
+ active: z4.number(),
5611
+ total: z4.number()
5612
+ }),
5613
+ examples: [{ description: "Count active and known members" }],
4972
5614
  async run(c) {
4973
5615
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4974
5616
  const [active, total] = await Promise.all([
@@ -4987,8 +5629,16 @@ members.command("count", {
4987
5629
  }
4988
5630
  });
4989
5631
  members.command("fees", {
4990
- description: "Read registry fee config.",
5632
+ description: "Get registration and heartbeat fee settings.",
4991
5633
  env: env4,
5634
+ output: z4.object({
5635
+ registrationFeeWei: z4.string(),
5636
+ registrationFee: z4.string(),
5637
+ heartbeatFeeWei: z4.string(),
5638
+ heartbeatFee: z4.string(),
5639
+ heartbeatGracePeriodSeconds: z4.number()
5640
+ }),
5641
+ examples: [{ description: "Inspect current registry fee configuration" }],
4992
5642
  async run(c) {
4993
5643
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
4994
5644
  const [registrationFee, heartbeatFee, heartbeatGracePeriod] = await Promise.all([
@@ -5020,10 +5670,21 @@ members.command("fees", {
5020
5670
 
5021
5671
  // src/commands/treasury.ts
5022
5672
  import { Cli as Cli5, z as z5 } from "incur";
5023
- var env5 = z5.object({ ABSTRACT_RPC_URL: z5.string().optional() });
5024
- var treasury = Cli5.create("treasury", { description: "Read treasury state." });
5673
+ var env5 = z5.object({
5674
+ ABSTRACT_RPC_URL: z5.string().optional().describe("Abstract RPC URL override")
5675
+ });
5676
+ var treasury = Cli5.create("treasury", {
5677
+ description: "Inspect treasury balances, execution status, and spend controls."
5678
+ });
5025
5679
  treasury.command("balance", {
5680
+ description: "Get current native token balance for the treasury contract.",
5026
5681
  env: env5,
5682
+ output: z5.object({
5683
+ address: z5.string(),
5684
+ balanceWei: z5.string(),
5685
+ balance: z5.string()
5686
+ }),
5687
+ examples: [{ description: "Check treasury balance" }],
5027
5688
  async run(c) {
5028
5689
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
5029
5690
  const balance = await client.getBalance({ address: ABSTRACT_MAINNET_ADDRESSES.treasury });
@@ -5035,8 +5696,21 @@ treasury.command("balance", {
5035
5696
  }
5036
5697
  });
5037
5698
  treasury.command("whitelist", {
5038
- args: z5.object({ asset: z5.string() }),
5699
+ description: "Check whether an asset address is treasury-whitelisted.",
5700
+ args: z5.object({
5701
+ asset: z5.string().describe("Token/asset contract address")
5702
+ }),
5039
5703
  env: env5,
5704
+ output: z5.object({
5705
+ asset: z5.string(),
5706
+ whitelisted: z5.boolean()
5707
+ }),
5708
+ examples: [
5709
+ {
5710
+ args: { asset: "0x0000000000000000000000000000000000000000" },
5711
+ description: "Check whitelist status for one asset"
5712
+ }
5713
+ ],
5040
5714
  async run(c) {
5041
5715
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
5042
5716
  const whitelisted = await client.readContract({
@@ -5049,7 +5723,15 @@ treasury.command("whitelist", {
5049
5723
  }
5050
5724
  });
5051
5725
  treasury.command("major-spend-status", {
5726
+ description: "Read major-spend cooldown status for the treasury contract.",
5052
5727
  env: env5,
5728
+ output: z5.object({
5729
+ majorSpendCooldownSeconds: z5.number(),
5730
+ lastMajorSpendAt: z5.number(),
5731
+ lastMajorSpendRelative: z5.string(),
5732
+ isMajorSpendAllowed: z5.boolean()
5733
+ }),
5734
+ examples: [{ description: "Inspect treasury major-spend guardrails" }],
5053
5735
  async run(c) {
5054
5736
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
5055
5737
  const [cooldown, lastMajorSpendAt, allowed] = await Promise.all([
@@ -5080,8 +5762,16 @@ treasury.command("major-spend-status", {
5080
5762
  }
5081
5763
  });
5082
5764
  treasury.command("executed", {
5083
- args: z5.object({ proposalId: z5.coerce.number().int().positive() }),
5765
+ description: "Check whether a treasury action for a proposal has executed.",
5766
+ args: z5.object({
5767
+ proposalId: z5.coerce.number().int().positive().describe("Governance proposal id")
5768
+ }),
5084
5769
  env: env5,
5770
+ output: z5.object({
5771
+ proposalId: z5.number(),
5772
+ executed: z5.boolean()
5773
+ }),
5774
+ examples: [{ args: { proposalId: 1 }, description: "Check execution status for proposal #1" }],
5085
5775
  async run(c) {
5086
5776
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
5087
5777
  const executed = await client.readContract({
@@ -5094,6 +5784,133 @@ treasury.command("executed", {
5094
5784
  }
5095
5785
  });
5096
5786
 
5787
+ // src/error-handling.ts
5788
+ import { AsyncLocalStorage } from "async_hooks";
5789
+ import { Errors } from "incur";
5790
+ var VIEM_VERSION_PATTERN = /\n*Version:\s*viem@[^\n]+/i;
5791
+ var VIEM_VERSION_PATTERN_GLOBAL = /\n*Version:\s*viem@[^\n]+/gi;
5792
+ var debugFlagStore = new AsyncLocalStorage();
5793
+ var VIEM_ERROR_NAMES = /* @__PURE__ */ new Set([
5794
+ "CallExecutionError",
5795
+ "ContractFunctionExecutionError",
5796
+ "ContractFunctionRevertedError",
5797
+ "HttpRequestError",
5798
+ "InvalidAddressError",
5799
+ "TransactionExecutionError"
5800
+ ]);
5801
+ function parseDebugFlag(argv) {
5802
+ const cleaned = [];
5803
+ let debug = false;
5804
+ for (let i = 0; i < argv.length; i += 1) {
5805
+ const token = argv[i];
5806
+ if (token === "--debug") {
5807
+ debug = true;
5808
+ continue;
5809
+ }
5810
+ if (token === "--debug=true" || token === "--debug=1") {
5811
+ debug = true;
5812
+ continue;
5813
+ }
5814
+ if (token === "--debug=false" || token === "--debug=0") {
5815
+ debug = false;
5816
+ continue;
5817
+ }
5818
+ cleaned.push(token);
5819
+ }
5820
+ return { argv: cleaned, debug };
5821
+ }
5822
+ function isViemLikeError(error) {
5823
+ if (!(error instanceof Error)) return false;
5824
+ const shortMessage = error.shortMessage;
5825
+ return VIEM_ERROR_NAMES.has(error.name) || VIEM_VERSION_PATTERN.test(error.message) || typeof shortMessage === "string" && error.message.includes("Docs: https://viem.sh");
5826
+ }
5827
+ function sanitizeViemMessage(message) {
5828
+ return message.replace(VIEM_VERSION_PATTERN_GLOBAL, "").trim();
5829
+ }
5830
+ function toFriendlyViemError(error) {
5831
+ const shortMessage = typeof error.shortMessage === "string" && error.shortMessage.trim().length > 0 ? error.shortMessage.trim() : void 0;
5832
+ if (error.name === "InvalidAddressError" || shortMessage?.startsWith('Address "')) {
5833
+ return {
5834
+ code: "INVALID_ADDRESS",
5835
+ message: `${shortMessage ?? "Invalid address."} Use a valid 0x-prefixed 20-byte address. Run with --debug for full error details.`
5836
+ };
5837
+ }
5838
+ if (shortMessage?.toLowerCase().includes("http request failed") || error.message.toLowerCase().includes("http request failed")) {
5839
+ return {
5840
+ code: "RPC_CONNECTION_FAILED",
5841
+ message: "RPC connection failed. Check ABSTRACT_RPC_URL and try again. Run with --debug for full error details."
5842
+ };
5843
+ }
5844
+ if (shortMessage || VIEM_VERSION_PATTERN.test(error.message)) {
5845
+ return {
5846
+ code: "UPSTREAM_ERROR",
5847
+ message: `${sanitizeViemMessage(shortMessage ?? error.message)} Run with --debug for full error details.`
5848
+ };
5849
+ }
5850
+ return void 0;
5851
+ }
5852
+ function isMissingRequiredArgValidation(error) {
5853
+ return error.fieldErrors.some((fieldError) => {
5854
+ const msg = fieldError.message.toLowerCase();
5855
+ return msg.includes("received undefined") || fieldError.received === "";
5856
+ });
5857
+ }
5858
+ function missingArgPaths(error) {
5859
+ const paths = /* @__PURE__ */ new Set();
5860
+ for (const fieldError of error.fieldErrors) {
5861
+ const msg = fieldError.message.toLowerCase();
5862
+ if (msg.includes("received undefined") || fieldError.received === "") {
5863
+ paths.add(fieldError.path);
5864
+ }
5865
+ }
5866
+ return [...paths];
5867
+ }
5868
+ function toFriendlyValidationError(ctx, error) {
5869
+ const missing = missingArgPaths(error);
5870
+ if (missing.length === 0) return void 0;
5871
+ const helpCommand = `${ctx.name} ${ctx.command} --help`;
5872
+ const argsList = missing.join(", ");
5873
+ return {
5874
+ code: "VALIDATION_ERROR",
5875
+ cta: {
5876
+ description: "See command usage:",
5877
+ commands: [{ command: helpCommand }]
5878
+ },
5879
+ message: missing.length === 1 ? `Missing required argument: ${argsList}. Run \`${helpCommand}\` for usage.` : `Missing required arguments: ${argsList}. Run \`${helpCommand}\` for usage.`
5880
+ };
5881
+ }
5882
+ function handleError(ctx, error) {
5883
+ if (debugFlagStore.getStore()) throw error;
5884
+ if (error instanceof Errors.ValidationError && isMissingRequiredArgValidation(error)) {
5885
+ const friendly = toFriendlyValidationError(ctx, error);
5886
+ if (friendly) {
5887
+ return ctx.error(friendly);
5888
+ }
5889
+ }
5890
+ if (isViemLikeError(error)) {
5891
+ const friendly = toFriendlyViemError(error);
5892
+ if (friendly) {
5893
+ return ctx.error(friendly);
5894
+ }
5895
+ }
5896
+ throw error;
5897
+ }
5898
+ function applyFriendlyErrorHandling(cli2) {
5899
+ const originalServe = cli2.serve.bind(cli2);
5900
+ cli2.serve = (async (argv, options) => {
5901
+ const rawArgv = argv ?? process.argv.slice(2);
5902
+ const parsed = parseDebugFlag(rawArgv);
5903
+ return debugFlagStore.run(parsed.debug, () => originalServe(parsed.argv, options));
5904
+ });
5905
+ cli2.use(async (context, next) => {
5906
+ try {
5907
+ await next();
5908
+ } catch (error) {
5909
+ return handleError(context, error);
5910
+ }
5911
+ });
5912
+ }
5913
+
5097
5914
  // src/cli.ts
5098
5915
  var cli = Cli6.create("assembly", {
5099
5916
  description: "Assembly governance CLI for Abstract chain."
@@ -5103,10 +5920,21 @@ cli.command(council);
5103
5920
  cli.command(forum);
5104
5921
  cli.command(governance);
5105
5922
  cli.command(treasury);
5106
- var rootEnv = z6.object({ ABSTRACT_RPC_URL: z6.string().optional() });
5923
+ var rootEnv = z6.object({
5924
+ ABSTRACT_RPC_URL: z6.string().optional().describe("Abstract RPC URL override")
5925
+ });
5107
5926
  cli.command("status", {
5108
- description: "Cross-contract status snapshot.",
5927
+ description: "Get a cross-contract Assembly snapshot (members, council, governance, treasury).",
5109
5928
  env: rootEnv,
5929
+ output: z6.object({
5930
+ activeMemberCount: z6.number(),
5931
+ seatCount: z6.number(),
5932
+ proposalCount: z6.number(),
5933
+ currentAuctionDay: z6.number(),
5934
+ currentAuctionSlot: z6.number(),
5935
+ treasuryBalance: z6.string()
5936
+ }),
5937
+ examples: [{ description: "Fetch the current Assembly system status" }],
5110
5938
  async run(c) {
5111
5939
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
5112
5940
  const [
@@ -5155,9 +5983,25 @@ cli.command("status", {
5155
5983
  }
5156
5984
  });
5157
5985
  cli.command("health", {
5158
- description: "Cross-contract health for one address.",
5159
- args: z6.object({ address: z6.string() }),
5986
+ description: "Check cross-contract health for one address (membership, council, refunds, power).",
5987
+ args: z6.object({
5988
+ address: z6.string().describe("Member or wallet address to inspect")
5989
+ }),
5160
5990
  env: rootEnv,
5991
+ output: z6.object({
5992
+ address: z6.string(),
5993
+ isActive: z6.boolean(),
5994
+ activeUntil: z6.number(),
5995
+ isCouncilMember: z6.boolean(),
5996
+ pendingReturnsWei: z6.string(),
5997
+ votingPower: z6.number()
5998
+ }),
5999
+ examples: [
6000
+ {
6001
+ args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
6002
+ description: "Inspect one address across Assembly contracts"
6003
+ }
6004
+ ],
5161
6005
  async run(c) {
5162
6006
  const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
5163
6007
  const [isActive, member, isCouncilMember, pendingReturns, votingPower] = await Promise.all([
@@ -5193,7 +6037,7 @@ cli.command("health", {
5193
6037
  })
5194
6038
  ]);
5195
6039
  return c.ok({
5196
- address: c.args.address,
6040
+ address: toChecksum(c.args.address),
5197
6041
  isActive,
5198
6042
  activeUntil: Number(member.activeUntil),
5199
6043
  isCouncilMember,
@@ -5202,6 +6046,7 @@ cli.command("health", {
5202
6046
  });
5203
6047
  }
5204
6048
  });
6049
+ applyFriendlyErrorHandling(cli);
5205
6050
  var isMain = process.argv[1] === fileURLToPath(import.meta.url);
5206
6051
  if (isMain) {
5207
6052
  cli.serve();