@spectratools/assembly-cli 0.8.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1102 -98
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5242,11 +5242,40 @@ council.command("withdraw-refund", {
|
|
|
5242
5242
|
});
|
|
5243
5243
|
|
|
5244
5244
|
// src/commands/forum.ts
|
|
5245
|
+
import { TxError } from "@spectratools/tx-shared";
|
|
5245
5246
|
import { Cli as Cli2, z as z3 } from "incur";
|
|
5246
5247
|
var env2 = z3.object({
|
|
5247
5248
|
ABSTRACT_RPC_URL: z3.string().optional().describe("Abstract RPC URL override")
|
|
5248
5249
|
});
|
|
5250
|
+
var commentEnv = env2.extend({
|
|
5251
|
+
PRIVATE_KEY: z3.string().optional().describe("Private key (required only when posting a comment via --body)")
|
|
5252
|
+
});
|
|
5249
5253
|
var timestampOutput2 = z3.union([z3.number(), z3.string()]);
|
|
5254
|
+
var txResultOutput2 = z3.union([
|
|
5255
|
+
z3.object({
|
|
5256
|
+
status: z3.literal("success"),
|
|
5257
|
+
hash: z3.string(),
|
|
5258
|
+
blockNumber: z3.number(),
|
|
5259
|
+
gasUsed: z3.string(),
|
|
5260
|
+
from: z3.string(),
|
|
5261
|
+
to: z3.string().nullable(),
|
|
5262
|
+
effectiveGasPrice: z3.string().optional()
|
|
5263
|
+
}),
|
|
5264
|
+
z3.object({
|
|
5265
|
+
status: z3.literal("reverted"),
|
|
5266
|
+
hash: z3.string(),
|
|
5267
|
+
blockNumber: z3.number(),
|
|
5268
|
+
gasUsed: z3.string(),
|
|
5269
|
+
from: z3.string(),
|
|
5270
|
+
to: z3.string().nullable(),
|
|
5271
|
+
effectiveGasPrice: z3.string().optional()
|
|
5272
|
+
}),
|
|
5273
|
+
z3.object({
|
|
5274
|
+
status: z3.literal("dry-run"),
|
|
5275
|
+
estimatedGas: z3.string(),
|
|
5276
|
+
simulationResult: z3.unknown()
|
|
5277
|
+
})
|
|
5278
|
+
]);
|
|
5250
5279
|
function decodeThread(value) {
|
|
5251
5280
|
const [id, kind, author, createdAt, category, title, body, proposalId, petitionId] = value;
|
|
5252
5281
|
return {
|
|
@@ -5356,7 +5385,7 @@ forum.command("threads", {
|
|
|
5356
5385
|
description: "Inspect or comment:",
|
|
5357
5386
|
commands: [
|
|
5358
5387
|
{ command: "forum thread", args: { id: "<id>" } },
|
|
5359
|
-
{ command: "forum post-comment", args: {
|
|
5388
|
+
{ command: "forum post-comment", args: { threadId: "<id>" } }
|
|
5360
5389
|
]
|
|
5361
5390
|
}
|
|
5362
5391
|
}
|
|
@@ -5451,34 +5480,395 @@ forum.command("comments", {
|
|
|
5451
5480
|
}
|
|
5452
5481
|
});
|
|
5453
5482
|
forum.command("comment", {
|
|
5454
|
-
description: "Get one comment by
|
|
5483
|
+
description: "Get one comment by id, or post to a thread when --body is provided.",
|
|
5455
5484
|
args: z3.object({
|
|
5456
|
-
id: z3.coerce.number().int().positive().describe("Comment id (
|
|
5485
|
+
id: z3.coerce.number().int().positive().describe("Comment id (read) or thread id (write)")
|
|
5457
5486
|
}),
|
|
5458
|
-
|
|
5487
|
+
options: writeOptions.extend({
|
|
5488
|
+
body: z3.string().min(1).optional().describe("Comment body (write mode)"),
|
|
5489
|
+
"parent-id": z3.coerce.number().int().nonnegative().default(0).describe("Optional parent comment id for threaded replies (write mode)")
|
|
5490
|
+
}),
|
|
5491
|
+
env: commentEnv,
|
|
5459
5492
|
output: z3.record(z3.string(), z3.unknown()),
|
|
5460
|
-
examples: [
|
|
5493
|
+
examples: [
|
|
5494
|
+
{ args: { id: 1 }, description: "Fetch comment #1" },
|
|
5495
|
+
{
|
|
5496
|
+
args: { id: 1 },
|
|
5497
|
+
options: { body: "I support this proposal." },
|
|
5498
|
+
description: "Post a new comment on thread #1"
|
|
5499
|
+
}
|
|
5500
|
+
],
|
|
5461
5501
|
async run(c) {
|
|
5462
5502
|
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
|
|
5503
|
+
if (!c.options.body) {
|
|
5504
|
+
const commentCount2 = await client.readContract({
|
|
5505
|
+
abi: forumAbi,
|
|
5506
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5507
|
+
functionName: "commentCount"
|
|
5508
|
+
});
|
|
5509
|
+
if (c.args.id > Number(commentCount2)) {
|
|
5510
|
+
return c.error({
|
|
5511
|
+
code: "OUT_OF_RANGE",
|
|
5512
|
+
message: `Comment id ${c.args.id} does not exist (commentCount: ${commentCount2})`,
|
|
5513
|
+
retryable: false
|
|
5514
|
+
});
|
|
5515
|
+
}
|
|
5516
|
+
const commentTuple = await client.readContract({
|
|
5517
|
+
abi: forumAbi,
|
|
5518
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5519
|
+
functionName: "comments",
|
|
5520
|
+
args: [BigInt(c.args.id)]
|
|
5521
|
+
});
|
|
5522
|
+
return c.ok(jsonSafe(decodeComment(commentTuple)));
|
|
5523
|
+
}
|
|
5524
|
+
if (!c.env.PRIVATE_KEY) {
|
|
5525
|
+
return c.error({
|
|
5526
|
+
code: "MISSING_PRIVATE_KEY",
|
|
5527
|
+
message: "PRIVATE_KEY is required when posting a comment.",
|
|
5528
|
+
retryable: false
|
|
5529
|
+
});
|
|
5530
|
+
}
|
|
5531
|
+
const account = resolveAccount({ PRIVATE_KEY: c.env.PRIVATE_KEY });
|
|
5532
|
+
const [activeMember, threadCount, commentCount] = await Promise.all([
|
|
5533
|
+
client.readContract({
|
|
5534
|
+
abi: registryAbi,
|
|
5535
|
+
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
5536
|
+
functionName: "isActive",
|
|
5537
|
+
args: [account.address]
|
|
5538
|
+
}),
|
|
5539
|
+
client.readContract({
|
|
5540
|
+
abi: forumAbi,
|
|
5541
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5542
|
+
functionName: "threadCount"
|
|
5543
|
+
}),
|
|
5544
|
+
client.readContract({
|
|
5545
|
+
abi: forumAbi,
|
|
5546
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5547
|
+
functionName: "commentCount"
|
|
5548
|
+
})
|
|
5549
|
+
]);
|
|
5550
|
+
if (!activeMember) {
|
|
5551
|
+
return c.error({
|
|
5552
|
+
code: "NOT_ACTIVE_MEMBER",
|
|
5553
|
+
message: `Address ${toChecksum(account.address)} is not an active Assembly member.`,
|
|
5554
|
+
retryable: false
|
|
5555
|
+
});
|
|
5556
|
+
}
|
|
5557
|
+
if (c.args.id > Number(threadCount)) {
|
|
5469
5558
|
return c.error({
|
|
5470
5559
|
code: "OUT_OF_RANGE",
|
|
5471
|
-
message: `
|
|
5560
|
+
message: `Thread id ${c.args.id} does not exist (threadCount: ${threadCount})`,
|
|
5472
5561
|
retryable: false
|
|
5473
5562
|
});
|
|
5474
5563
|
}
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5564
|
+
if (c.options["parent-id"] > Number(commentCount)) {
|
|
5565
|
+
return c.error({
|
|
5566
|
+
code: "OUT_OF_RANGE",
|
|
5567
|
+
message: `Parent comment id ${c.options["parent-id"]} does not exist (commentCount: ${commentCount})`,
|
|
5568
|
+
retryable: false
|
|
5569
|
+
});
|
|
5570
|
+
}
|
|
5571
|
+
const expectedCommentId = Number(commentCount) + 1;
|
|
5572
|
+
try {
|
|
5573
|
+
const txResult = await assemblyWriteTx({
|
|
5574
|
+
env: {
|
|
5575
|
+
PRIVATE_KEY: c.env.PRIVATE_KEY,
|
|
5576
|
+
ABSTRACT_RPC_URL: c.env.ABSTRACT_RPC_URL
|
|
5577
|
+
},
|
|
5578
|
+
options: c.options,
|
|
5579
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5580
|
+
abi: forumAbi,
|
|
5581
|
+
functionName: "postComment",
|
|
5582
|
+
args: [BigInt(c.args.id), BigInt(c.options["parent-id"]), c.options.body]
|
|
5583
|
+
});
|
|
5584
|
+
return c.ok({
|
|
5585
|
+
author: toChecksum(account.address),
|
|
5586
|
+
threadId: c.args.id,
|
|
5587
|
+
parentId: c.options["parent-id"],
|
|
5588
|
+
expectedCommentId,
|
|
5589
|
+
tx: txResult
|
|
5590
|
+
});
|
|
5591
|
+
} catch (error) {
|
|
5592
|
+
if (error instanceof TxError) {
|
|
5593
|
+
return c.error({
|
|
5594
|
+
code: error.code,
|
|
5595
|
+
message: error.message,
|
|
5596
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
5597
|
+
});
|
|
5598
|
+
}
|
|
5599
|
+
throw error;
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
});
|
|
5603
|
+
forum.command("post", {
|
|
5604
|
+
description: "Create a new discussion thread in the forum.",
|
|
5605
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
5606
|
+
options: writeOptions.extend({
|
|
5607
|
+
category: z3.string().min(1).describe("Thread category label (e.g., general, governance)"),
|
|
5608
|
+
title: z3.string().min(1).describe("Thread title"),
|
|
5609
|
+
body: z3.string().min(1).describe("Thread body")
|
|
5610
|
+
}),
|
|
5611
|
+
env: writeEnv,
|
|
5612
|
+
output: z3.object({
|
|
5613
|
+
author: z3.string(),
|
|
5614
|
+
category: z3.string(),
|
|
5615
|
+
title: z3.string(),
|
|
5616
|
+
expectedThreadId: z3.number(),
|
|
5617
|
+
tx: txResultOutput2
|
|
5618
|
+
}),
|
|
5619
|
+
examples: [
|
|
5620
|
+
{
|
|
5621
|
+
options: {
|
|
5622
|
+
category: "general",
|
|
5623
|
+
title: "Roadmap discussion",
|
|
5624
|
+
body: "Should we prioritize treasury automation in Q2?"
|
|
5625
|
+
},
|
|
5626
|
+
description: "Post a new discussion thread"
|
|
5627
|
+
}
|
|
5628
|
+
],
|
|
5629
|
+
async run(c) {
|
|
5630
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
5631
|
+
const account = resolveAccount(c.env);
|
|
5632
|
+
const [activeMember, threadCountBefore] = await Promise.all([
|
|
5633
|
+
client.readContract({
|
|
5634
|
+
abi: registryAbi,
|
|
5635
|
+
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
5636
|
+
functionName: "isActive",
|
|
5637
|
+
args: [account.address]
|
|
5638
|
+
}),
|
|
5639
|
+
client.readContract({
|
|
5640
|
+
abi: forumAbi,
|
|
5641
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5642
|
+
functionName: "threadCount"
|
|
5643
|
+
})
|
|
5644
|
+
]);
|
|
5645
|
+
if (!activeMember) {
|
|
5646
|
+
return c.error({
|
|
5647
|
+
code: "NOT_ACTIVE_MEMBER",
|
|
5648
|
+
message: `Address ${toChecksum(account.address)} is not an active Assembly member.`,
|
|
5649
|
+
retryable: false
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
const expectedThreadId = Number(threadCountBefore) + 1;
|
|
5653
|
+
try {
|
|
5654
|
+
const txResult = await assemblyWriteTx({
|
|
5655
|
+
env: c.env,
|
|
5656
|
+
options: c.options,
|
|
5657
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5658
|
+
abi: forumAbi,
|
|
5659
|
+
functionName: "postDiscussionThread",
|
|
5660
|
+
args: [c.options.category, c.options.title, c.options.body]
|
|
5661
|
+
});
|
|
5662
|
+
return c.ok({
|
|
5663
|
+
author: toChecksum(account.address),
|
|
5664
|
+
category: c.options.category,
|
|
5665
|
+
title: c.options.title,
|
|
5666
|
+
expectedThreadId,
|
|
5667
|
+
tx: txResult
|
|
5668
|
+
});
|
|
5669
|
+
} catch (error) {
|
|
5670
|
+
if (error instanceof TxError) {
|
|
5671
|
+
return c.error({
|
|
5672
|
+
code: error.code,
|
|
5673
|
+
message: error.message,
|
|
5674
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
5675
|
+
});
|
|
5676
|
+
}
|
|
5677
|
+
throw error;
|
|
5678
|
+
}
|
|
5679
|
+
}
|
|
5680
|
+
});
|
|
5681
|
+
forum.command("post-comment", {
|
|
5682
|
+
description: "Post a comment to a forum thread.",
|
|
5683
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
5684
|
+
args: z3.object({
|
|
5685
|
+
threadId: z3.coerce.number().int().positive().describe("Thread id to comment on")
|
|
5686
|
+
}),
|
|
5687
|
+
options: writeOptions.extend({
|
|
5688
|
+
body: z3.string().min(1).describe("Comment body"),
|
|
5689
|
+
"parent-id": z3.coerce.number().int().nonnegative().default(0).describe("Optional parent comment id for threaded replies")
|
|
5690
|
+
}),
|
|
5691
|
+
env: writeEnv,
|
|
5692
|
+
output: z3.object({
|
|
5693
|
+
author: z3.string(),
|
|
5694
|
+
threadId: z3.number(),
|
|
5695
|
+
parentId: z3.number(),
|
|
5696
|
+
expectedCommentId: z3.number(),
|
|
5697
|
+
tx: txResultOutput2
|
|
5698
|
+
}),
|
|
5699
|
+
examples: [
|
|
5700
|
+
{
|
|
5701
|
+
args: { threadId: 1 },
|
|
5702
|
+
options: {
|
|
5703
|
+
body: "Appreciate the update \u2014 support from me."
|
|
5704
|
+
},
|
|
5705
|
+
description: "Post a comment on thread #1"
|
|
5706
|
+
}
|
|
5707
|
+
],
|
|
5708
|
+
async run(c) {
|
|
5709
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
5710
|
+
const account = resolveAccount(c.env);
|
|
5711
|
+
const [activeMember, threadCount, commentCount] = await Promise.all([
|
|
5712
|
+
client.readContract({
|
|
5713
|
+
abi: registryAbi,
|
|
5714
|
+
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
5715
|
+
functionName: "isActive",
|
|
5716
|
+
args: [account.address]
|
|
5717
|
+
}),
|
|
5718
|
+
client.readContract({
|
|
5719
|
+
abi: forumAbi,
|
|
5720
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5721
|
+
functionName: "threadCount"
|
|
5722
|
+
}),
|
|
5723
|
+
client.readContract({
|
|
5724
|
+
abi: forumAbi,
|
|
5725
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5726
|
+
functionName: "commentCount"
|
|
5727
|
+
})
|
|
5728
|
+
]);
|
|
5729
|
+
if (!activeMember) {
|
|
5730
|
+
return c.error({
|
|
5731
|
+
code: "NOT_ACTIVE_MEMBER",
|
|
5732
|
+
message: `Address ${toChecksum(account.address)} is not an active Assembly member.`,
|
|
5733
|
+
retryable: false
|
|
5734
|
+
});
|
|
5735
|
+
}
|
|
5736
|
+
if (c.args.threadId > Number(threadCount)) {
|
|
5737
|
+
return c.error({
|
|
5738
|
+
code: "OUT_OF_RANGE",
|
|
5739
|
+
message: `Thread id ${c.args.threadId} does not exist (threadCount: ${threadCount})`,
|
|
5740
|
+
retryable: false
|
|
5741
|
+
});
|
|
5742
|
+
}
|
|
5743
|
+
if (c.options["parent-id"] > Number(commentCount)) {
|
|
5744
|
+
return c.error({
|
|
5745
|
+
code: "OUT_OF_RANGE",
|
|
5746
|
+
message: `Parent comment id ${c.options["parent-id"]} does not exist (commentCount: ${commentCount})`,
|
|
5747
|
+
retryable: false
|
|
5748
|
+
});
|
|
5749
|
+
}
|
|
5750
|
+
const expectedCommentId = Number(commentCount) + 1;
|
|
5751
|
+
try {
|
|
5752
|
+
const txResult = await assemblyWriteTx({
|
|
5753
|
+
env: c.env,
|
|
5754
|
+
options: c.options,
|
|
5755
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5756
|
+
abi: forumAbi,
|
|
5757
|
+
functionName: "postComment",
|
|
5758
|
+
args: [BigInt(c.args.threadId), BigInt(c.options["parent-id"]), c.options.body]
|
|
5759
|
+
});
|
|
5760
|
+
return c.ok({
|
|
5761
|
+
author: toChecksum(account.address),
|
|
5762
|
+
threadId: c.args.threadId,
|
|
5763
|
+
parentId: c.options["parent-id"],
|
|
5764
|
+
expectedCommentId,
|
|
5765
|
+
tx: txResult
|
|
5766
|
+
});
|
|
5767
|
+
} catch (error) {
|
|
5768
|
+
if (error instanceof TxError) {
|
|
5769
|
+
return c.error({
|
|
5770
|
+
code: error.code,
|
|
5771
|
+
message: error.message,
|
|
5772
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
5773
|
+
});
|
|
5774
|
+
}
|
|
5775
|
+
throw error;
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
});
|
|
5779
|
+
forum.command("sign-petition", {
|
|
5780
|
+
description: "Sign an existing petition as an active member.",
|
|
5781
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
5782
|
+
args: z3.object({
|
|
5783
|
+
petitionId: z3.coerce.number().int().positive().describe("Petition id (1-indexed)")
|
|
5784
|
+
}),
|
|
5785
|
+
options: writeOptions,
|
|
5786
|
+
env: writeEnv,
|
|
5787
|
+
output: z3.object({
|
|
5788
|
+
signer: z3.string(),
|
|
5789
|
+
petitionId: z3.number(),
|
|
5790
|
+
expectedSignatures: z3.number(),
|
|
5791
|
+
tx: txResultOutput2
|
|
5792
|
+
}),
|
|
5793
|
+
examples: [{ args: { petitionId: 1 }, description: "Sign petition #1" }],
|
|
5794
|
+
async run(c) {
|
|
5795
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
5796
|
+
const account = resolveAccount(c.env);
|
|
5797
|
+
const [activeMember, petitionCount] = await Promise.all([
|
|
5798
|
+
client.readContract({
|
|
5799
|
+
abi: registryAbi,
|
|
5800
|
+
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
5801
|
+
functionName: "isActive",
|
|
5802
|
+
args: [account.address]
|
|
5803
|
+
}),
|
|
5804
|
+
client.readContract({
|
|
5805
|
+
abi: forumAbi,
|
|
5806
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5807
|
+
functionName: "petitionCount"
|
|
5808
|
+
})
|
|
5809
|
+
]);
|
|
5810
|
+
if (!activeMember) {
|
|
5811
|
+
return c.error({
|
|
5812
|
+
code: "NOT_ACTIVE_MEMBER",
|
|
5813
|
+
message: `Address ${toChecksum(account.address)} is not an active Assembly member.`,
|
|
5814
|
+
retryable: false
|
|
5815
|
+
});
|
|
5816
|
+
}
|
|
5817
|
+
if (c.args.petitionId > Number(petitionCount)) {
|
|
5818
|
+
return c.error({
|
|
5819
|
+
code: "OUT_OF_RANGE",
|
|
5820
|
+
message: `Petition id ${c.args.petitionId} does not exist (petitionCount: ${petitionCount})`,
|
|
5821
|
+
retryable: false
|
|
5822
|
+
});
|
|
5823
|
+
}
|
|
5824
|
+
const [alreadySigned, petitionTuple] = await Promise.all([
|
|
5825
|
+
client.readContract({
|
|
5826
|
+
abi: forumAbi,
|
|
5827
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5828
|
+
functionName: "hasSignedPetition",
|
|
5829
|
+
args: [BigInt(c.args.petitionId), account.address]
|
|
5830
|
+
}),
|
|
5831
|
+
client.readContract({
|
|
5832
|
+
abi: forumAbi,
|
|
5833
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5834
|
+
functionName: "petitions",
|
|
5835
|
+
args: [BigInt(c.args.petitionId)]
|
|
5836
|
+
})
|
|
5837
|
+
]);
|
|
5838
|
+
if (alreadySigned) {
|
|
5839
|
+
return c.error({
|
|
5840
|
+
code: "ALREADY_SIGNED",
|
|
5841
|
+
message: `Address ${toChecksum(account.address)} has already signed petition #${c.args.petitionId}.`,
|
|
5842
|
+
retryable: false
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
const petition = decodePetition(petitionTuple);
|
|
5846
|
+
const expectedSignatures = petition.signatures + 1;
|
|
5847
|
+
try {
|
|
5848
|
+
const txResult = await assemblyWriteTx({
|
|
5849
|
+
env: c.env,
|
|
5850
|
+
options: c.options,
|
|
5851
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
5852
|
+
abi: forumAbi,
|
|
5853
|
+
functionName: "signPetition",
|
|
5854
|
+
args: [BigInt(c.args.petitionId)]
|
|
5855
|
+
});
|
|
5856
|
+
return c.ok({
|
|
5857
|
+
signer: toChecksum(account.address),
|
|
5858
|
+
petitionId: c.args.petitionId,
|
|
5859
|
+
expectedSignatures,
|
|
5860
|
+
tx: txResult
|
|
5861
|
+
});
|
|
5862
|
+
} catch (error) {
|
|
5863
|
+
if (error instanceof TxError) {
|
|
5864
|
+
return c.error({
|
|
5865
|
+
code: error.code,
|
|
5866
|
+
message: error.message,
|
|
5867
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
5868
|
+
});
|
|
5869
|
+
}
|
|
5870
|
+
throw error;
|
|
5871
|
+
}
|
|
5482
5872
|
}
|
|
5483
5873
|
});
|
|
5484
5874
|
forum.command("petitions", {
|
|
@@ -5622,6 +6012,7 @@ forum.command("stats", {
|
|
|
5622
6012
|
});
|
|
5623
6013
|
|
|
5624
6014
|
// src/commands/governance.ts
|
|
6015
|
+
import { TxError as TxError2 } from "@spectratools/tx-shared";
|
|
5625
6016
|
import { Cli as Cli3, z as z4 } from "incur";
|
|
5626
6017
|
var env3 = z4.object({
|
|
5627
6018
|
ABSTRACT_RPC_URL: z4.string().optional().describe("Abstract RPC URL override")
|
|
@@ -5635,6 +6026,14 @@ var proposalStatusLabels = {
|
|
|
5635
6026
|
4: "defeated",
|
|
5636
6027
|
5: "cancelled"
|
|
5637
6028
|
};
|
|
6029
|
+
var PROPOSAL_STATUS_PENDING = 0;
|
|
6030
|
+
var PROPOSAL_STATUS_ACTIVE = 1;
|
|
6031
|
+
var PROPOSAL_STATUS_PASSED = 2;
|
|
6032
|
+
var supportChoiceToValue = {
|
|
6033
|
+
against: 0,
|
|
6034
|
+
for: 1,
|
|
6035
|
+
abstain: 2
|
|
6036
|
+
};
|
|
5638
6037
|
function proposalStatus(status) {
|
|
5639
6038
|
const statusCode = asNum(status);
|
|
5640
6039
|
return {
|
|
@@ -5749,6 +6148,23 @@ function serializeProposal(proposal) {
|
|
|
5749
6148
|
description: proposal.description
|
|
5750
6149
|
};
|
|
5751
6150
|
}
|
|
6151
|
+
async function readProposalCount(client) {
|
|
6152
|
+
return await client.readContract({
|
|
6153
|
+
abi: governanceAbi,
|
|
6154
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6155
|
+
functionName: "proposalCount"
|
|
6156
|
+
});
|
|
6157
|
+
}
|
|
6158
|
+
async function readProposalById(client, proposalId) {
|
|
6159
|
+
return decodeProposal(
|
|
6160
|
+
await client.readContract({
|
|
6161
|
+
abi: governanceAbi,
|
|
6162
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6163
|
+
functionName: "proposals",
|
|
6164
|
+
args: [BigInt(proposalId)]
|
|
6165
|
+
})
|
|
6166
|
+
);
|
|
6167
|
+
}
|
|
5752
6168
|
var governance = Cli3.create("governance", {
|
|
5753
6169
|
description: "Inspect Assembly governance proposals, votes, and parameters."
|
|
5754
6170
|
});
|
|
@@ -5806,7 +6222,10 @@ governance.command("proposals", {
|
|
|
5806
6222
|
description: "Inspect or vote:",
|
|
5807
6223
|
commands: [
|
|
5808
6224
|
{ command: "governance proposal", args: { id: "<id>" } },
|
|
5809
|
-
{
|
|
6225
|
+
{
|
|
6226
|
+
command: "governance vote",
|
|
6227
|
+
args: { proposalId: "<id>", support: "<for|against|abstain>" }
|
|
6228
|
+
}
|
|
5810
6229
|
]
|
|
5811
6230
|
}
|
|
5812
6231
|
}
|
|
@@ -5846,103 +6265,475 @@ governance.command("proposal", {
|
|
|
5846
6265
|
return c.ok(serializeProposal(proposal));
|
|
5847
6266
|
}
|
|
5848
6267
|
});
|
|
5849
|
-
governance.command("has-voted", {
|
|
5850
|
-
description: "Check if an address has voted on a proposal.",
|
|
6268
|
+
governance.command("has-voted", {
|
|
6269
|
+
description: "Check if an address has voted on a proposal.",
|
|
6270
|
+
args: z4.object({
|
|
6271
|
+
proposalId: z4.coerce.number().int().positive().describe("Proposal id (1-indexed)"),
|
|
6272
|
+
address: z4.string().describe("Voter address")
|
|
6273
|
+
}),
|
|
6274
|
+
env: env3,
|
|
6275
|
+
output: z4.object({
|
|
6276
|
+
proposalId: z4.number(),
|
|
6277
|
+
address: z4.string(),
|
|
6278
|
+
hasVoted: z4.boolean()
|
|
6279
|
+
}),
|
|
6280
|
+
examples: [
|
|
6281
|
+
{
|
|
6282
|
+
args: {
|
|
6283
|
+
proposalId: 1,
|
|
6284
|
+
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
|
|
6285
|
+
},
|
|
6286
|
+
description: "Check whether an address already voted"
|
|
6287
|
+
}
|
|
6288
|
+
],
|
|
6289
|
+
async run(c) {
|
|
6290
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
6291
|
+
const hasVoted = await client.readContract({
|
|
6292
|
+
abi: governanceAbi,
|
|
6293
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6294
|
+
functionName: "hasVoted",
|
|
6295
|
+
args: [BigInt(c.args.proposalId), c.args.address]
|
|
6296
|
+
});
|
|
6297
|
+
return c.ok({
|
|
6298
|
+
proposalId: c.args.proposalId,
|
|
6299
|
+
address: toChecksum(c.args.address),
|
|
6300
|
+
hasVoted
|
|
6301
|
+
});
|
|
6302
|
+
}
|
|
6303
|
+
});
|
|
6304
|
+
governance.command("params", {
|
|
6305
|
+
description: "Read governance threshold and timing parameters.",
|
|
6306
|
+
env: env3,
|
|
6307
|
+
output: z4.object({
|
|
6308
|
+
deliberationPeriod: z4.number(),
|
|
6309
|
+
votePeriod: z4.number(),
|
|
6310
|
+
quorumBps: z4.number(),
|
|
6311
|
+
constitutionalDeliberationPeriod: z4.number(),
|
|
6312
|
+
constitutionalVotePeriod: z4.number(),
|
|
6313
|
+
constitutionalPassBps: z4.number(),
|
|
6314
|
+
majorPassBps: z4.number(),
|
|
6315
|
+
parameterPassBps: z4.number(),
|
|
6316
|
+
significantPassBps: z4.number(),
|
|
6317
|
+
significantThresholdBps: z4.number(),
|
|
6318
|
+
routineThresholdBps: z4.number(),
|
|
6319
|
+
timelockPeriod: z4.number()
|
|
6320
|
+
}),
|
|
6321
|
+
examples: [{ description: "Inspect governance timing and pass thresholds" }],
|
|
6322
|
+
async run(c) {
|
|
6323
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
6324
|
+
const getters = [
|
|
6325
|
+
"deliberationPeriod",
|
|
6326
|
+
"votePeriod",
|
|
6327
|
+
"quorumBps",
|
|
6328
|
+
"constitutionalDeliberationPeriod",
|
|
6329
|
+
"constitutionalVotePeriod",
|
|
6330
|
+
"constitutionalPassBps",
|
|
6331
|
+
"majorPassBps",
|
|
6332
|
+
"parameterPassBps",
|
|
6333
|
+
"significantPassBps",
|
|
6334
|
+
"significantThresholdBps",
|
|
6335
|
+
"routineThresholdBps",
|
|
6336
|
+
"timelockPeriod"
|
|
6337
|
+
];
|
|
6338
|
+
const values = await client.multicall({
|
|
6339
|
+
allowFailure: false,
|
|
6340
|
+
contracts: getters.map((name) => ({
|
|
6341
|
+
abi: governanceAbi,
|
|
6342
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6343
|
+
functionName: name
|
|
6344
|
+
}))
|
|
6345
|
+
});
|
|
6346
|
+
return c.ok({
|
|
6347
|
+
deliberationPeriod: asNum(values[0]),
|
|
6348
|
+
votePeriod: asNum(values[1]),
|
|
6349
|
+
quorumBps: asNum(values[2]),
|
|
6350
|
+
constitutionalDeliberationPeriod: asNum(values[3]),
|
|
6351
|
+
constitutionalVotePeriod: asNum(values[4]),
|
|
6352
|
+
constitutionalPassBps: asNum(values[5]),
|
|
6353
|
+
majorPassBps: asNum(values[6]),
|
|
6354
|
+
parameterPassBps: asNum(values[7]),
|
|
6355
|
+
significantPassBps: asNum(values[8]),
|
|
6356
|
+
significantThresholdBps: asNum(values[9]),
|
|
6357
|
+
routineThresholdBps: asNum(values[10]),
|
|
6358
|
+
timelockPeriod: asNum(values[11])
|
|
6359
|
+
});
|
|
6360
|
+
}
|
|
6361
|
+
});
|
|
6362
|
+
var txResultOutput3 = z4.union([
|
|
6363
|
+
z4.object({
|
|
6364
|
+
status: z4.enum(["success", "reverted"]),
|
|
6365
|
+
hash: z4.string(),
|
|
6366
|
+
blockNumber: z4.number(),
|
|
6367
|
+
gasUsed: z4.string(),
|
|
6368
|
+
from: z4.string(),
|
|
6369
|
+
to: z4.string().nullable(),
|
|
6370
|
+
effectiveGasPrice: z4.string().optional()
|
|
6371
|
+
}),
|
|
6372
|
+
z4.object({
|
|
6373
|
+
status: z4.literal("dry-run"),
|
|
6374
|
+
estimatedGas: z4.string(),
|
|
6375
|
+
simulationResult: z4.unknown()
|
|
6376
|
+
})
|
|
6377
|
+
]);
|
|
6378
|
+
governance.command("vote", {
|
|
6379
|
+
description: "Cast a governance vote on a proposal.",
|
|
6380
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
6381
|
+
args: z4.object({
|
|
6382
|
+
proposalId: z4.coerce.number().int().positive().describe("Proposal id (1-indexed)"),
|
|
6383
|
+
support: z4.enum(["for", "against", "abstain"]).describe("Vote support: for, against, or abstain")
|
|
6384
|
+
}),
|
|
6385
|
+
options: writeOptions,
|
|
6386
|
+
env: writeEnv,
|
|
6387
|
+
output: z4.object({
|
|
6388
|
+
proposalId: z4.number(),
|
|
6389
|
+
proposalTitle: z4.string(),
|
|
6390
|
+
support: z4.enum(["for", "against", "abstain"]),
|
|
6391
|
+
supportValue: z4.number(),
|
|
6392
|
+
tx: txResultOutput3
|
|
6393
|
+
}),
|
|
6394
|
+
examples: [
|
|
6395
|
+
{
|
|
6396
|
+
args: { proposalId: 1, support: "for" },
|
|
6397
|
+
description: "Vote in favor of proposal #1"
|
|
6398
|
+
},
|
|
6399
|
+
{
|
|
6400
|
+
args: { proposalId: 1, support: "abstain" },
|
|
6401
|
+
options: { "dry-run": true },
|
|
6402
|
+
description: "Simulate casting an abstain vote"
|
|
6403
|
+
}
|
|
6404
|
+
],
|
|
6405
|
+
async run(c) {
|
|
6406
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
6407
|
+
const account = resolveAccount(c.env);
|
|
6408
|
+
const proposalCount = await readProposalCount(client);
|
|
6409
|
+
if (c.args.proposalId > Number(proposalCount)) {
|
|
6410
|
+
return c.error({
|
|
6411
|
+
code: "OUT_OF_RANGE",
|
|
6412
|
+
message: `Proposal id ${c.args.proposalId} does not exist (proposalCount: ${proposalCount})`,
|
|
6413
|
+
retryable: false
|
|
6414
|
+
});
|
|
6415
|
+
}
|
|
6416
|
+
const proposal = await readProposalById(client, c.args.proposalId);
|
|
6417
|
+
const status = proposalStatus(proposal.status);
|
|
6418
|
+
if (status.statusCode !== PROPOSAL_STATUS_ACTIVE) {
|
|
6419
|
+
return c.error({
|
|
6420
|
+
code: "PROPOSAL_NOT_VOTING",
|
|
6421
|
+
message: `Proposal ${c.args.proposalId} is ${status.status} and cannot be voted right now.`,
|
|
6422
|
+
retryable: false
|
|
6423
|
+
});
|
|
6424
|
+
}
|
|
6425
|
+
const hasVoted = await client.readContract({
|
|
6426
|
+
abi: governanceAbi,
|
|
6427
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6428
|
+
functionName: "hasVoted",
|
|
6429
|
+
args: [BigInt(c.args.proposalId), account.address]
|
|
6430
|
+
});
|
|
6431
|
+
if (hasVoted) {
|
|
6432
|
+
return c.error({
|
|
6433
|
+
code: "ALREADY_VOTED",
|
|
6434
|
+
message: `Address ${toChecksum(account.address)} has already voted on proposal ${c.args.proposalId}.`,
|
|
6435
|
+
retryable: false
|
|
6436
|
+
});
|
|
6437
|
+
}
|
|
6438
|
+
const supportValue = supportChoiceToValue[c.args.support];
|
|
6439
|
+
try {
|
|
6440
|
+
const txResult = await assemblyWriteTx({
|
|
6441
|
+
env: c.env,
|
|
6442
|
+
options: c.options,
|
|
6443
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6444
|
+
abi: governanceAbi,
|
|
6445
|
+
functionName: "castVote",
|
|
6446
|
+
args: [BigInt(c.args.proposalId), supportValue]
|
|
6447
|
+
});
|
|
6448
|
+
return c.ok({
|
|
6449
|
+
proposalId: c.args.proposalId,
|
|
6450
|
+
proposalTitle: proposal.title,
|
|
6451
|
+
support: c.args.support,
|
|
6452
|
+
supportValue,
|
|
6453
|
+
tx: txResult
|
|
6454
|
+
});
|
|
6455
|
+
} catch (error) {
|
|
6456
|
+
if (error instanceof TxError2) {
|
|
6457
|
+
return c.error({
|
|
6458
|
+
code: error.code,
|
|
6459
|
+
message: error.message,
|
|
6460
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
6461
|
+
});
|
|
6462
|
+
}
|
|
6463
|
+
throw error;
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
});
|
|
6467
|
+
governance.command("propose", {
|
|
6468
|
+
description: "Create a new council-originated governance proposal.",
|
|
6469
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
6470
|
+
options: writeOptions.extend({
|
|
6471
|
+
title: z4.string().min(1).describe("Proposal title"),
|
|
6472
|
+
description: z4.string().min(1).describe("Proposal description"),
|
|
6473
|
+
kind: z4.coerce.number().int().nonnegative().max(255).describe("Proposal kind enum value"),
|
|
6474
|
+
category: z4.string().default("governance").describe("Forum category label for the proposal"),
|
|
6475
|
+
"risk-tier": z4.coerce.number().int().nonnegative().max(255).optional().describe("Optional max allowed intent risk tier (default: 0)"),
|
|
6476
|
+
amount: z4.string().optional().describe("Optional treasury amount hint (currently unsupported for intent encoding)"),
|
|
6477
|
+
recipient: z4.string().optional().describe("Optional treasury recipient hint (currently unsupported for intent encoding)")
|
|
6478
|
+
}),
|
|
6479
|
+
env: writeEnv,
|
|
6480
|
+
output: z4.object({
|
|
6481
|
+
proposer: z4.string(),
|
|
6482
|
+
category: z4.string(),
|
|
6483
|
+
kind: z4.number(),
|
|
6484
|
+
title: z4.string(),
|
|
6485
|
+
description: z4.string(),
|
|
6486
|
+
expectedProposalId: z4.number(),
|
|
6487
|
+
tx: txResultOutput3
|
|
6488
|
+
}),
|
|
6489
|
+
examples: [
|
|
6490
|
+
{
|
|
6491
|
+
options: {
|
|
6492
|
+
title: "Increase quorum requirement",
|
|
6493
|
+
description: "Raise quorum from 10% to 12% for governance votes.",
|
|
6494
|
+
kind: 3
|
|
6495
|
+
},
|
|
6496
|
+
description: "Create a governance proposal from a council member account"
|
|
6497
|
+
}
|
|
6498
|
+
],
|
|
6499
|
+
async run(c) {
|
|
6500
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
6501
|
+
const account = resolveAccount(c.env);
|
|
6502
|
+
if (c.options.amount && !c.options.recipient || !c.options.amount && c.options.recipient) {
|
|
6503
|
+
return c.error({
|
|
6504
|
+
code: "INVALID_PROPOSAL_OPTIONS",
|
|
6505
|
+
message: "Both --amount and --recipient must be provided together when setting transfer hints.",
|
|
6506
|
+
retryable: false
|
|
6507
|
+
});
|
|
6508
|
+
}
|
|
6509
|
+
if (c.options.amount || c.options.recipient) {
|
|
6510
|
+
return c.error({
|
|
6511
|
+
code: "UNSUPPORTED_TRANSFER_INTENT",
|
|
6512
|
+
message: "Transfer intents are not yet supported by `governance propose`. Omit --amount/--recipient and use intent-specific tooling.",
|
|
6513
|
+
retryable: false
|
|
6514
|
+
});
|
|
6515
|
+
}
|
|
6516
|
+
const isCouncilMember = await client.readContract({
|
|
6517
|
+
abi: councilSeatsAbi,
|
|
6518
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
6519
|
+
functionName: "isCouncilMember",
|
|
6520
|
+
args: [account.address]
|
|
6521
|
+
});
|
|
6522
|
+
if (!isCouncilMember) {
|
|
6523
|
+
return c.error({
|
|
6524
|
+
code: "NOT_COUNCIL_MEMBER",
|
|
6525
|
+
message: `Address ${toChecksum(account.address)} is not an active council member and cannot create a council proposal.`,
|
|
6526
|
+
retryable: false
|
|
6527
|
+
});
|
|
6528
|
+
}
|
|
6529
|
+
const proposalCountBefore = await readProposalCount(client);
|
|
6530
|
+
const expectedProposalId = Number(proposalCountBefore) + 1;
|
|
6531
|
+
const proposalInput = {
|
|
6532
|
+
kind: c.options.kind,
|
|
6533
|
+
title: c.options.title,
|
|
6534
|
+
description: c.options.description,
|
|
6535
|
+
intentSteps: [],
|
|
6536
|
+
intentConstraints: {
|
|
6537
|
+
deadline: 0,
|
|
6538
|
+
maxAllowedRiskTier: c.options["risk-tier"] ?? 0
|
|
6539
|
+
},
|
|
6540
|
+
configUpdates: []
|
|
6541
|
+
};
|
|
6542
|
+
try {
|
|
6543
|
+
const txResult = await assemblyWriteTx({
|
|
6544
|
+
env: c.env,
|
|
6545
|
+
options: c.options,
|
|
6546
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
6547
|
+
abi: forumAbi,
|
|
6548
|
+
functionName: "createCouncilProposal",
|
|
6549
|
+
args: [c.options.category, proposalInput]
|
|
6550
|
+
});
|
|
6551
|
+
return c.ok({
|
|
6552
|
+
proposer: toChecksum(account.address),
|
|
6553
|
+
category: c.options.category,
|
|
6554
|
+
kind: c.options.kind,
|
|
6555
|
+
title: c.options.title,
|
|
6556
|
+
description: c.options.description,
|
|
6557
|
+
expectedProposalId,
|
|
6558
|
+
tx: txResult
|
|
6559
|
+
});
|
|
6560
|
+
} catch (error) {
|
|
6561
|
+
if (error instanceof TxError2) {
|
|
6562
|
+
return c.error({
|
|
6563
|
+
code: error.code,
|
|
6564
|
+
message: error.message,
|
|
6565
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
6566
|
+
});
|
|
6567
|
+
}
|
|
6568
|
+
throw error;
|
|
6569
|
+
}
|
|
6570
|
+
}
|
|
6571
|
+
});
|
|
6572
|
+
governance.command("queue", {
|
|
6573
|
+
description: "Finalize voting and queue an eligible proposal into timelock.",
|
|
6574
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
5851
6575
|
args: z4.object({
|
|
5852
|
-
proposalId: z4.coerce.number().int().positive().describe("Proposal id (1-indexed)")
|
|
5853
|
-
address: z4.string().describe("Voter address")
|
|
6576
|
+
proposalId: z4.coerce.number().int().positive().describe("Proposal id (1-indexed)")
|
|
5854
6577
|
}),
|
|
5855
|
-
|
|
6578
|
+
options: writeOptions,
|
|
6579
|
+
env: writeEnv,
|
|
5856
6580
|
output: z4.object({
|
|
5857
6581
|
proposalId: z4.number(),
|
|
5858
|
-
|
|
5859
|
-
|
|
6582
|
+
proposalTitle: z4.string(),
|
|
6583
|
+
statusBefore: z4.string(),
|
|
6584
|
+
tx: txResultOutput3
|
|
5860
6585
|
}),
|
|
5861
6586
|
examples: [
|
|
5862
6587
|
{
|
|
5863
|
-
args: {
|
|
5864
|
-
|
|
5865
|
-
address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
|
|
5866
|
-
},
|
|
5867
|
-
description: "Check whether an address already voted"
|
|
6588
|
+
args: { proposalId: 1 },
|
|
6589
|
+
description: "Finalize voting for proposal #1 and queue if passed"
|
|
5868
6590
|
}
|
|
5869
6591
|
],
|
|
5870
6592
|
async run(c) {
|
|
5871
6593
|
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
5872
|
-
const
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
6594
|
+
const proposalCount = await readProposalCount(client);
|
|
6595
|
+
if (c.args.proposalId > Number(proposalCount)) {
|
|
6596
|
+
return c.error({
|
|
6597
|
+
code: "OUT_OF_RANGE",
|
|
6598
|
+
message: `Proposal id ${c.args.proposalId} does not exist (proposalCount: ${proposalCount})`,
|
|
6599
|
+
retryable: false
|
|
6600
|
+
});
|
|
6601
|
+
}
|
|
6602
|
+
const proposal = await readProposalById(client, c.args.proposalId);
|
|
6603
|
+
const status = proposalStatus(proposal.status);
|
|
6604
|
+
if (status.statusCode === PROPOSAL_STATUS_PASSED) {
|
|
6605
|
+
return c.error({
|
|
6606
|
+
code: "ALREADY_QUEUED",
|
|
6607
|
+
message: `Proposal ${c.args.proposalId} is already queued in timelock.`,
|
|
6608
|
+
retryable: false
|
|
6609
|
+
});
|
|
6610
|
+
}
|
|
6611
|
+
if (status.statusCode === PROPOSAL_STATUS_PENDING) {
|
|
6612
|
+
return c.error({
|
|
6613
|
+
code: "PROPOSAL_NOT_QUEUEABLE",
|
|
6614
|
+
message: `Proposal ${c.args.proposalId} is still in deliberation and cannot be queued yet.`,
|
|
6615
|
+
retryable: false
|
|
6616
|
+
});
|
|
6617
|
+
}
|
|
6618
|
+
if (status.statusCode !== PROPOSAL_STATUS_ACTIVE) {
|
|
6619
|
+
return c.error({
|
|
6620
|
+
code: "PROPOSAL_NOT_QUEUEABLE",
|
|
6621
|
+
message: `Proposal ${c.args.proposalId} is ${status.status} and cannot be queued.`,
|
|
6622
|
+
retryable: false
|
|
6623
|
+
});
|
|
6624
|
+
}
|
|
6625
|
+
const latestBlock = await client.getBlock({ blockTag: "latest" });
|
|
6626
|
+
if (latestBlock.timestamp < proposal.voteEndAt) {
|
|
6627
|
+
return c.error({
|
|
6628
|
+
code: "VOTING_STILL_ACTIVE",
|
|
6629
|
+
message: `Proposal ${c.args.proposalId} voting window is still open (ends ${relTime(proposal.voteEndAt)}).`,
|
|
6630
|
+
retryable: false
|
|
6631
|
+
});
|
|
6632
|
+
}
|
|
6633
|
+
try {
|
|
6634
|
+
const txResult = await assemblyWriteTx({
|
|
6635
|
+
env: c.env,
|
|
6636
|
+
options: c.options,
|
|
6637
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
6638
|
+
abi: governanceAbi,
|
|
6639
|
+
functionName: "finalizeVote",
|
|
6640
|
+
args: [BigInt(c.args.proposalId)]
|
|
6641
|
+
});
|
|
6642
|
+
return c.ok({
|
|
6643
|
+
proposalId: c.args.proposalId,
|
|
6644
|
+
proposalTitle: proposal.title,
|
|
6645
|
+
statusBefore: status.status,
|
|
6646
|
+
tx: txResult
|
|
6647
|
+
});
|
|
6648
|
+
} catch (error) {
|
|
6649
|
+
if (error instanceof TxError2) {
|
|
6650
|
+
return c.error({
|
|
6651
|
+
code: error.code,
|
|
6652
|
+
message: error.message,
|
|
6653
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
6654
|
+
});
|
|
6655
|
+
}
|
|
6656
|
+
throw error;
|
|
6657
|
+
}
|
|
5883
6658
|
}
|
|
5884
6659
|
});
|
|
5885
|
-
governance.command("
|
|
5886
|
-
description: "
|
|
5887
|
-
|
|
6660
|
+
governance.command("execute", {
|
|
6661
|
+
description: "Execute a queued governance proposal after timelock expiry.",
|
|
6662
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
6663
|
+
args: z4.object({
|
|
6664
|
+
proposalId: z4.coerce.number().int().positive().describe("Proposal id (1-indexed)")
|
|
6665
|
+
}),
|
|
6666
|
+
options: writeOptions,
|
|
6667
|
+
env: writeEnv,
|
|
5888
6668
|
output: z4.object({
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
constitutionalVotePeriod: z4.number(),
|
|
5894
|
-
constitutionalPassBps: z4.number(),
|
|
5895
|
-
majorPassBps: z4.number(),
|
|
5896
|
-
parameterPassBps: z4.number(),
|
|
5897
|
-
significantPassBps: z4.number(),
|
|
5898
|
-
significantThresholdBps: z4.number(),
|
|
5899
|
-
routineThresholdBps: z4.number(),
|
|
5900
|
-
timelockPeriod: z4.number()
|
|
6669
|
+
proposalId: z4.number(),
|
|
6670
|
+
proposalTitle: z4.string(),
|
|
6671
|
+
timelockEndsAt: timestampOutput3,
|
|
6672
|
+
tx: txResultOutput3
|
|
5901
6673
|
}),
|
|
5902
|
-
examples: [
|
|
6674
|
+
examples: [
|
|
6675
|
+
{
|
|
6676
|
+
args: { proposalId: 1 },
|
|
6677
|
+
description: "Execute proposal #1 after timelock has expired"
|
|
6678
|
+
}
|
|
6679
|
+
],
|
|
5903
6680
|
async run(c) {
|
|
5904
6681
|
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
5905
|
-
const
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
6682
|
+
const proposalCount = await readProposalCount(client);
|
|
6683
|
+
if (c.args.proposalId > Number(proposalCount)) {
|
|
6684
|
+
return c.error({
|
|
6685
|
+
code: "OUT_OF_RANGE",
|
|
6686
|
+
message: `Proposal id ${c.args.proposalId} does not exist (proposalCount: ${proposalCount})`,
|
|
6687
|
+
retryable: false
|
|
6688
|
+
});
|
|
6689
|
+
}
|
|
6690
|
+
const proposal = await readProposalById(client, c.args.proposalId);
|
|
6691
|
+
const status = proposalStatus(proposal.status);
|
|
6692
|
+
if (status.statusCode !== PROPOSAL_STATUS_PASSED) {
|
|
6693
|
+
return c.error({
|
|
6694
|
+
code: "PROPOSAL_NOT_EXECUTABLE",
|
|
6695
|
+
message: `Proposal ${c.args.proposalId} is ${status.status} and cannot be executed.`,
|
|
6696
|
+
retryable: false
|
|
6697
|
+
});
|
|
6698
|
+
}
|
|
6699
|
+
const latestBlock = await client.getBlock({ blockTag: "latest" });
|
|
6700
|
+
if (latestBlock.timestamp < proposal.timelockEndsAt) {
|
|
6701
|
+
return c.error({
|
|
6702
|
+
code: "TIMELOCK_ACTIVE",
|
|
6703
|
+
message: `Proposal ${c.args.proposalId} timelock has not expired yet (ends ${relTime(proposal.timelockEndsAt)}).`,
|
|
6704
|
+
retryable: false
|
|
6705
|
+
});
|
|
6706
|
+
}
|
|
6707
|
+
try {
|
|
6708
|
+
const txResult = await assemblyWriteTx({
|
|
6709
|
+
env: c.env,
|
|
6710
|
+
options: c.options,
|
|
5923
6711
|
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
6712
|
+
abi: governanceAbi,
|
|
6713
|
+
functionName: "executeProposal",
|
|
6714
|
+
args: [BigInt(c.args.proposalId)]
|
|
6715
|
+
});
|
|
6716
|
+
return c.ok({
|
|
6717
|
+
proposalId: c.args.proposalId,
|
|
6718
|
+
proposalTitle: proposal.title,
|
|
6719
|
+
timelockEndsAt: timeValue(proposal.timelockEndsAt, c.format),
|
|
6720
|
+
tx: txResult
|
|
6721
|
+
});
|
|
6722
|
+
} catch (error) {
|
|
6723
|
+
if (error instanceof TxError2) {
|
|
6724
|
+
return c.error({
|
|
6725
|
+
code: error.code,
|
|
6726
|
+
message: error.message,
|
|
6727
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
6728
|
+
});
|
|
6729
|
+
}
|
|
6730
|
+
throw error;
|
|
6731
|
+
}
|
|
5941
6732
|
}
|
|
5942
6733
|
});
|
|
5943
6734
|
|
|
5944
6735
|
// src/commands/members.ts
|
|
5945
|
-
import { TxError } from "@spectratools/tx-shared";
|
|
6736
|
+
import { TxError as TxError3 } from "@spectratools/tx-shared";
|
|
5946
6737
|
import { Cli as Cli4, z as z5 } from "incur";
|
|
5947
6738
|
var DEFAULT_MEMBER_SNAPSHOT_URL = "https://www.theaiassembly.org/api/indexer/members";
|
|
5948
6739
|
var REGISTERED_EVENT_SCAN_STEP = 100000n;
|
|
@@ -6435,7 +7226,7 @@ members.command("register", {
|
|
|
6435
7226
|
} : void 0
|
|
6436
7227
|
);
|
|
6437
7228
|
} catch (error) {
|
|
6438
|
-
if (error instanceof
|
|
7229
|
+
if (error instanceof TxError3 && error.code === "INSUFFICIENT_FUNDS") {
|
|
6439
7230
|
return c.error({
|
|
6440
7231
|
code: "INSUFFICIENT_FUNDS",
|
|
6441
7232
|
message: `Insufficient funds to register. Required fee: ${eth(fee)} (${fee} wei). ${error.message}`,
|
|
@@ -6486,7 +7277,7 @@ members.command("heartbeat", {
|
|
|
6486
7277
|
} : void 0
|
|
6487
7278
|
);
|
|
6488
7279
|
} catch (error) {
|
|
6489
|
-
if (error instanceof
|
|
7280
|
+
if (error instanceof TxError3 && error.code === "INSUFFICIENT_FUNDS") {
|
|
6490
7281
|
return c.error({
|
|
6491
7282
|
code: "INSUFFICIENT_FUNDS",
|
|
6492
7283
|
message: `Insufficient funds for heartbeat. Required fee: ${eth(fee)} (${fee} wei). ${error.message}`,
|
|
@@ -6537,7 +7328,7 @@ members.command("renew", {
|
|
|
6537
7328
|
} : void 0
|
|
6538
7329
|
);
|
|
6539
7330
|
} catch (error) {
|
|
6540
|
-
if (error instanceof
|
|
7331
|
+
if (error instanceof TxError3 && error.code === "INSUFFICIENT_FUNDS") {
|
|
6541
7332
|
return c.error({
|
|
6542
7333
|
code: "INSUFFICIENT_FUNDS",
|
|
6543
7334
|
message: `Insufficient funds to renew. Required fee: ${eth(fee)} (${fee} wei). ${error.message}`,
|
|
@@ -6550,11 +7341,38 @@ members.command("renew", {
|
|
|
6550
7341
|
});
|
|
6551
7342
|
|
|
6552
7343
|
// src/commands/treasury.ts
|
|
7344
|
+
import { TxError as TxError4 } from "@spectratools/tx-shared";
|
|
6553
7345
|
import { Cli as Cli5, z as z6 } from "incur";
|
|
7346
|
+
import { encodeAbiParameters, parseUnits, zeroAddress } from "viem";
|
|
6554
7347
|
var env5 = z6.object({
|
|
6555
7348
|
ABSTRACT_RPC_URL: z6.string().optional().describe("Abstract RPC URL override")
|
|
6556
7349
|
});
|
|
6557
7350
|
var timestampOutput5 = z6.union([z6.number(), z6.string()]);
|
|
7351
|
+
var txResultOutput4 = z6.union([
|
|
7352
|
+
z6.object({
|
|
7353
|
+
status: z6.literal("success"),
|
|
7354
|
+
hash: z6.string(),
|
|
7355
|
+
blockNumber: z6.number(),
|
|
7356
|
+
gasUsed: z6.string(),
|
|
7357
|
+
from: z6.string(),
|
|
7358
|
+
to: z6.string().nullable(),
|
|
7359
|
+
effectiveGasPrice: z6.string().optional()
|
|
7360
|
+
}),
|
|
7361
|
+
z6.object({
|
|
7362
|
+
status: z6.literal("reverted"),
|
|
7363
|
+
hash: z6.string(),
|
|
7364
|
+
blockNumber: z6.number(),
|
|
7365
|
+
gasUsed: z6.string(),
|
|
7366
|
+
from: z6.string(),
|
|
7367
|
+
to: z6.string().nullable(),
|
|
7368
|
+
effectiveGasPrice: z6.string().optional()
|
|
7369
|
+
}),
|
|
7370
|
+
z6.object({
|
|
7371
|
+
status: z6.literal("dry-run"),
|
|
7372
|
+
estimatedGas: z6.string(),
|
|
7373
|
+
simulationResult: z6.unknown()
|
|
7374
|
+
})
|
|
7375
|
+
]);
|
|
6558
7376
|
var treasury = Cli5.create("treasury", {
|
|
6559
7377
|
description: "Inspect treasury balances, execution status, and spend controls."
|
|
6560
7378
|
});
|
|
@@ -6677,6 +7495,192 @@ treasury.command("executed", {
|
|
|
6677
7495
|
return c.ok({ proposalId: c.args.proposalId, executed });
|
|
6678
7496
|
}
|
|
6679
7497
|
});
|
|
7498
|
+
treasury.command("propose-spend", {
|
|
7499
|
+
description: "Create a council proposal that spends treasury funds via TreasuryTransferIntentModule.",
|
|
7500
|
+
hint: "Requires PRIVATE_KEY environment variable for signing.",
|
|
7501
|
+
options: writeOptions.extend({
|
|
7502
|
+
token: z6.string().describe("Token address to spend (use 0x0000000000000000000000000000000000000000 for ETH)"),
|
|
7503
|
+
recipient: z6.string().describe("Recipient address"),
|
|
7504
|
+
amount: z6.string().describe("Token amount as decimal string (human units)"),
|
|
7505
|
+
decimals: z6.coerce.number().int().min(0).max(36).default(18).describe("Token decimals used to parse --amount (default: 18)"),
|
|
7506
|
+
title: z6.string().min(1).describe("Proposal title"),
|
|
7507
|
+
description: z6.string().min(1).describe("Proposal description"),
|
|
7508
|
+
category: z6.string().default("treasury").describe("Forum category label for this proposal"),
|
|
7509
|
+
"risk-tier": z6.coerce.number().int().min(0).max(3).default(3).describe("Max allowed risk tier in intent constraints (0-3, default: 3)")
|
|
7510
|
+
}),
|
|
7511
|
+
env: writeEnv,
|
|
7512
|
+
output: z6.object({
|
|
7513
|
+
proposer: z6.string(),
|
|
7514
|
+
category: z6.string(),
|
|
7515
|
+
token: z6.string(),
|
|
7516
|
+
recipient: z6.string(),
|
|
7517
|
+
amount: z6.string(),
|
|
7518
|
+
amountWei: z6.string(),
|
|
7519
|
+
expectedProposalId: z6.number(),
|
|
7520
|
+
expectedThreadId: z6.number(),
|
|
7521
|
+
tx: txResultOutput4
|
|
7522
|
+
}),
|
|
7523
|
+
examples: [
|
|
7524
|
+
{
|
|
7525
|
+
options: {
|
|
7526
|
+
token: "0x0000000000000000000000000000000000000000",
|
|
7527
|
+
recipient: "0x00000000000000000000000000000000000000b0",
|
|
7528
|
+
amount: "0.5",
|
|
7529
|
+
title: "Fund grants round",
|
|
7530
|
+
description: "Allocate 0.5 ETH from treasury to the grants multisig."
|
|
7531
|
+
},
|
|
7532
|
+
description: "Propose a treasury spend transfer"
|
|
7533
|
+
}
|
|
7534
|
+
],
|
|
7535
|
+
async run(c) {
|
|
7536
|
+
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
7537
|
+
const account = resolveAccount(c.env);
|
|
7538
|
+
const token = toChecksum(c.options.token);
|
|
7539
|
+
const recipient = toChecksum(c.options.recipient);
|
|
7540
|
+
let amountWei;
|
|
7541
|
+
try {
|
|
7542
|
+
amountWei = parseUnits(c.options.amount, c.options.decimals);
|
|
7543
|
+
} catch {
|
|
7544
|
+
return c.error({
|
|
7545
|
+
code: "INVALID_AMOUNT",
|
|
7546
|
+
message: `Invalid amount "${c.options.amount}" for decimals=${c.options.decimals}.`,
|
|
7547
|
+
retryable: false
|
|
7548
|
+
});
|
|
7549
|
+
}
|
|
7550
|
+
if (amountWei <= 0n) {
|
|
7551
|
+
return c.error({
|
|
7552
|
+
code: "INVALID_AMOUNT",
|
|
7553
|
+
message: "--amount must be greater than zero.",
|
|
7554
|
+
retryable: false
|
|
7555
|
+
});
|
|
7556
|
+
}
|
|
7557
|
+
const [activeMember, isCouncilMember, transferModule, proposalCount, threadCount, whitelisted] = await Promise.all([
|
|
7558
|
+
client.readContract({
|
|
7559
|
+
abi: registryAbi,
|
|
7560
|
+
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
7561
|
+
functionName: "isActive",
|
|
7562
|
+
args: [account.address]
|
|
7563
|
+
}),
|
|
7564
|
+
client.readContract({
|
|
7565
|
+
abi: councilSeatsAbi,
|
|
7566
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
7567
|
+
functionName: "isCouncilMember",
|
|
7568
|
+
args: [account.address]
|
|
7569
|
+
}),
|
|
7570
|
+
client.readContract({
|
|
7571
|
+
abi: treasuryAbi,
|
|
7572
|
+
address: ABSTRACT_MAINNET_ADDRESSES.treasury,
|
|
7573
|
+
functionName: "treasuryTransferModule"
|
|
7574
|
+
}),
|
|
7575
|
+
client.readContract({
|
|
7576
|
+
abi: governanceAbi,
|
|
7577
|
+
address: ABSTRACT_MAINNET_ADDRESSES.governance,
|
|
7578
|
+
functionName: "proposalCount"
|
|
7579
|
+
}),
|
|
7580
|
+
client.readContract({
|
|
7581
|
+
abi: forumAbi,
|
|
7582
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
7583
|
+
functionName: "threadCount"
|
|
7584
|
+
}),
|
|
7585
|
+
token === zeroAddress ? Promise.resolve(true) : client.readContract({
|
|
7586
|
+
abi: treasuryAbi,
|
|
7587
|
+
address: ABSTRACT_MAINNET_ADDRESSES.treasury,
|
|
7588
|
+
functionName: "isAssetWhitelisted",
|
|
7589
|
+
args: [token]
|
|
7590
|
+
})
|
|
7591
|
+
]);
|
|
7592
|
+
if (!activeMember) {
|
|
7593
|
+
return c.error({
|
|
7594
|
+
code: "NOT_ACTIVE_MEMBER",
|
|
7595
|
+
message: `Address ${toChecksum(account.address)} is not an active Assembly member.`,
|
|
7596
|
+
retryable: false
|
|
7597
|
+
});
|
|
7598
|
+
}
|
|
7599
|
+
if (!isCouncilMember) {
|
|
7600
|
+
return c.error({
|
|
7601
|
+
code: "NOT_COUNCIL_MEMBER",
|
|
7602
|
+
message: `Address ${toChecksum(account.address)} is not an active council member and cannot create treasury spend proposals.`,
|
|
7603
|
+
retryable: false
|
|
7604
|
+
});
|
|
7605
|
+
}
|
|
7606
|
+
if (!whitelisted) {
|
|
7607
|
+
return c.error({
|
|
7608
|
+
code: "ASSET_NOT_WHITELISTED",
|
|
7609
|
+
message: `Token ${token} is not treasury-whitelisted for spending.`,
|
|
7610
|
+
retryable: false
|
|
7611
|
+
});
|
|
7612
|
+
}
|
|
7613
|
+
const transferModuleAllowed = await client.readContract({
|
|
7614
|
+
abi: treasuryAbi,
|
|
7615
|
+
address: ABSTRACT_MAINNET_ADDRESSES.treasury,
|
|
7616
|
+
functionName: "isIntentModuleAllowed",
|
|
7617
|
+
args: [transferModule]
|
|
7618
|
+
});
|
|
7619
|
+
if (!transferModuleAllowed) {
|
|
7620
|
+
return c.error({
|
|
7621
|
+
code: "INTENT_MODULE_DISABLED",
|
|
7622
|
+
message: `Treasury transfer intent module ${toChecksum(transferModule)} is currently disabled.`,
|
|
7623
|
+
retryable: false
|
|
7624
|
+
});
|
|
7625
|
+
}
|
|
7626
|
+
const moduleData = encodeAbiParameters(
|
|
7627
|
+
[
|
|
7628
|
+
{ name: "asset", type: "address" },
|
|
7629
|
+
{ name: "recipient", type: "address" },
|
|
7630
|
+
{ name: "amount", type: "uint256" }
|
|
7631
|
+
],
|
|
7632
|
+
[token, recipient, amountWei]
|
|
7633
|
+
);
|
|
7634
|
+
const proposalInput = {
|
|
7635
|
+
kind: 2,
|
|
7636
|
+
title: c.options.title,
|
|
7637
|
+
description: c.options.description,
|
|
7638
|
+
intentSteps: [
|
|
7639
|
+
{
|
|
7640
|
+
module: transferModule,
|
|
7641
|
+
moduleData
|
|
7642
|
+
}
|
|
7643
|
+
],
|
|
7644
|
+
intentConstraints: {
|
|
7645
|
+
deadline: 0,
|
|
7646
|
+
maxAllowedRiskTier: c.options["risk-tier"]
|
|
7647
|
+
},
|
|
7648
|
+
configUpdates: []
|
|
7649
|
+
};
|
|
7650
|
+
const expectedProposalId = Number(proposalCount) + 1;
|
|
7651
|
+
const expectedThreadId = Number(threadCount) + 1;
|
|
7652
|
+
try {
|
|
7653
|
+
const txResult = await assemblyWriteTx({
|
|
7654
|
+
env: c.env,
|
|
7655
|
+
options: c.options,
|
|
7656
|
+
address: ABSTRACT_MAINNET_ADDRESSES.forum,
|
|
7657
|
+
abi: forumAbi,
|
|
7658
|
+
functionName: "createCouncilProposal",
|
|
7659
|
+
args: [c.options.category, proposalInput]
|
|
7660
|
+
});
|
|
7661
|
+
return c.ok({
|
|
7662
|
+
proposer: toChecksum(account.address),
|
|
7663
|
+
category: c.options.category,
|
|
7664
|
+
token,
|
|
7665
|
+
recipient,
|
|
7666
|
+
amount: c.options.amount,
|
|
7667
|
+
amountWei: amountWei.toString(),
|
|
7668
|
+
expectedProposalId,
|
|
7669
|
+
expectedThreadId,
|
|
7670
|
+
tx: txResult
|
|
7671
|
+
});
|
|
7672
|
+
} catch (error) {
|
|
7673
|
+
if (error instanceof TxError4) {
|
|
7674
|
+
return c.error({
|
|
7675
|
+
code: error.code,
|
|
7676
|
+
message: error.message,
|
|
7677
|
+
retryable: error.code === "NONCE_CONFLICT"
|
|
7678
|
+
});
|
|
7679
|
+
}
|
|
7680
|
+
throw error;
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
});
|
|
6680
7684
|
|
|
6681
7685
|
// src/error-handling.ts
|
|
6682
7686
|
import { AsyncLocalStorage } from "async_hooks";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectratools/assembly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "CLI for Assembly governance on Abstract (members, council, forum, proposals, and treasury).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"ox": "^0.14.0",
|
|
34
34
|
"viem": "^2.47.0",
|
|
35
35
|
"@spectratools/cli-shared": "0.1.1",
|
|
36
|
-
"@spectratools/tx-shared": "0.
|
|
36
|
+
"@spectratools/tx-shared": "0.5.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"typescript": "5.7.3",
|