@spectratools/assembly-cli 0.7.0 → 0.8.2
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/README.md +23 -0
- package/dist/cli.js +246 -54
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -33,6 +33,29 @@ assembly-cli mcp add
|
|
|
33
33
|
| `ABSTRACT_RPC_URL` | No | Abstract RPC URL override (default from package client) |
|
|
34
34
|
| `ASSEMBLY_INDEXER_URL` | No | Optional member snapshot endpoint for `members list` (falls back to on-chain `Registered` events with a warning if unavailable) |
|
|
35
35
|
|
|
36
|
+
## Shared tx integration pattern (`@spectratools/tx-shared`)
|
|
37
|
+
|
|
38
|
+
`assembly-cli` write flows are powered by `@spectratools/tx-shared` (`executeTx` lifecycle + structured `TxError`s).
|
|
39
|
+
|
|
40
|
+
If you're building a consumer CLI command (or extending Assembly write behavior), use the shared pattern:
|
|
41
|
+
|
|
42
|
+
1. Parse signer flags/env via `toSignerOptions(...)`
|
|
43
|
+
2. Resolve signer with `resolveSigner(...)`
|
|
44
|
+
3. Build write request (`address`, `abi`, `functionName`, `args`/`value`)
|
|
45
|
+
4. Execute with `executeTx(...)` and handle `dryRun` + `TxError.code`
|
|
46
|
+
|
|
47
|
+
Reference wiring:
|
|
48
|
+
|
|
49
|
+
- Assembly example: [`src/examples/tx-shared-register.ts`](./src/examples/tx-shared-register.ts)
|
|
50
|
+
- tx-shared docs: [`../tx-shared/README.md`](../tx-shared/README.md)
|
|
51
|
+
- tx-shared assembly-style example: [`../tx-shared/src/examples/assembly-write.ts`](../tx-shared/src/examples/assembly-write.ts)
|
|
52
|
+
|
|
53
|
+
Current `assembly-cli` write commands require `PRIVATE_KEY`, but tx-shared supports broader provider setup for consumer CLIs:
|
|
54
|
+
|
|
55
|
+
- private key (`PRIVATE_KEY`)
|
|
56
|
+
- keystore (`--keystore` + `--password` or `KEYSTORE_PASSWORD`)
|
|
57
|
+
- Privy (`PRIVY_APP_ID`, `PRIVY_WALLET_ID`, `PRIVY_AUTHORIZATION_KEY`; implementation tracked in [#117](https://github.com/spectra-the-bot/spectra-tools/issues/117))
|
|
58
|
+
|
|
36
59
|
## Command Group Intent Summary
|
|
37
60
|
|
|
38
61
|
- `members` — Membership state, counts, and registry fee settings
|
package/dist/cli.js
CHANGED
|
@@ -4410,10 +4410,14 @@ function decodeAuction(value) {
|
|
|
4410
4410
|
const [highestBidder, highestBid, settled] = value;
|
|
4411
4411
|
return { highestBidder, highestBid, settled };
|
|
4412
4412
|
}
|
|
4413
|
-
function
|
|
4414
|
-
if (params.settled) return "settled";
|
|
4415
|
-
|
|
4416
|
-
|
|
4413
|
+
function deriveAuctionExecutionState(params) {
|
|
4414
|
+
if (params.settled) return { executionStatus: "settled", bidExecutableNow: false };
|
|
4415
|
+
const isCurrentAuctionSlot = params.day === Number(params.currentAuctionDay) && params.slot === Number(params.currentAuctionSlot);
|
|
4416
|
+
if (params.currentTimestamp < params.windowEnd) {
|
|
4417
|
+
if (isCurrentAuctionSlot) return { executionStatus: "open_now", bidExecutableNow: true };
|
|
4418
|
+
return { executionStatus: "upcoming", bidExecutableNow: false };
|
|
4419
|
+
}
|
|
4420
|
+
return { executionStatus: "closed_unsettled", bidExecutableNow: false };
|
|
4417
4421
|
}
|
|
4418
4422
|
var council = Cli.create("council", {
|
|
4419
4423
|
description: "Inspect council seats, members, auctions, and seat parameters."
|
|
@@ -4629,7 +4633,9 @@ council.command("auctions", {
|
|
|
4629
4633
|
settled: z2.boolean(),
|
|
4630
4634
|
windowEnd: timestampOutput,
|
|
4631
4635
|
windowEndRelative: z2.string(),
|
|
4632
|
-
|
|
4636
|
+
bidExecutableNow: z2.boolean(),
|
|
4637
|
+
executionStatus: z2.enum(["open_now", "upcoming", "closed_unsettled", "settled"]),
|
|
4638
|
+
status: z2.enum(["open_now", "upcoming", "closed_unsettled", "settled"])
|
|
4633
4639
|
})
|
|
4634
4640
|
)
|
|
4635
4641
|
}),
|
|
@@ -4687,19 +4693,27 @@ council.command("auctions", {
|
|
|
4687
4693
|
currentSlot: asNum(slot),
|
|
4688
4694
|
auctions: recent.map((x, i) => {
|
|
4689
4695
|
const windowEnd = windowEnds[i];
|
|
4696
|
+
const dayValue = Number(x.day);
|
|
4697
|
+
const execution = deriveAuctionExecutionState({
|
|
4698
|
+
day: dayValue,
|
|
4699
|
+
slot: x.slot,
|
|
4700
|
+
currentAuctionDay: day,
|
|
4701
|
+
currentAuctionSlot: slot,
|
|
4702
|
+
settled: auctions[i].settled,
|
|
4703
|
+
windowEnd,
|
|
4704
|
+
currentTimestamp
|
|
4705
|
+
});
|
|
4690
4706
|
return {
|
|
4691
|
-
day:
|
|
4707
|
+
day: dayValue,
|
|
4692
4708
|
slot: x.slot,
|
|
4693
4709
|
highestBidder: toChecksum(auctions[i].highestBidder),
|
|
4694
4710
|
highestBid: eth(auctions[i].highestBid),
|
|
4695
4711
|
settled: auctions[i].settled,
|
|
4696
4712
|
windowEnd: timeValue(windowEnd, c.format),
|
|
4697
4713
|
windowEndRelative: relTime(windowEnd),
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
currentTimestamp
|
|
4702
|
-
})
|
|
4714
|
+
bidExecutableNow: execution.bidExecutableNow,
|
|
4715
|
+
executionStatus: execution.executionStatus,
|
|
4716
|
+
status: execution.executionStatus
|
|
4703
4717
|
};
|
|
4704
4718
|
})
|
|
4705
4719
|
},
|
|
@@ -4730,7 +4744,9 @@ council.command("auction", {
|
|
|
4730
4744
|
settled: z2.boolean(),
|
|
4731
4745
|
windowEnd: timestampOutput,
|
|
4732
4746
|
windowEndRelative: z2.string(),
|
|
4733
|
-
|
|
4747
|
+
bidExecutableNow: z2.boolean(),
|
|
4748
|
+
executionStatus: z2.enum(["open_now", "upcoming", "closed_unsettled", "settled"]),
|
|
4749
|
+
status: z2.enum(["open_now", "upcoming", "closed_unsettled", "settled"])
|
|
4734
4750
|
}),
|
|
4735
4751
|
examples: [{ args: { day: 0, slot: 0 }, description: "Inspect day 0, slot 0 auction" }],
|
|
4736
4752
|
async run(c) {
|
|
@@ -4747,7 +4763,7 @@ council.command("auction", {
|
|
|
4747
4763
|
retryable: false
|
|
4748
4764
|
});
|
|
4749
4765
|
}
|
|
4750
|
-
const [auctionTuple, windowEnd, latestBlock] = await Promise.all([
|
|
4766
|
+
const [auctionTuple, windowEnd, latestBlock, currentAuctionDay, currentAuctionSlot] = await Promise.all([
|
|
4751
4767
|
client.readContract({
|
|
4752
4768
|
abi: councilSeatsAbi,
|
|
4753
4769
|
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
@@ -4760,11 +4776,30 @@ council.command("auction", {
|
|
|
4760
4776
|
functionName: "auctionWindowEnd",
|
|
4761
4777
|
args: [BigInt(c.args.day), c.args.slot]
|
|
4762
4778
|
}),
|
|
4763
|
-
client.getBlock({ blockTag: "latest" })
|
|
4779
|
+
client.getBlock({ blockTag: "latest" }),
|
|
4780
|
+
client.readContract({
|
|
4781
|
+
abi: councilSeatsAbi,
|
|
4782
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4783
|
+
functionName: "currentAuctionDay"
|
|
4784
|
+
}),
|
|
4785
|
+
client.readContract({
|
|
4786
|
+
abi: councilSeatsAbi,
|
|
4787
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4788
|
+
functionName: "currentAuctionSlot"
|
|
4789
|
+
})
|
|
4764
4790
|
]);
|
|
4765
4791
|
const auction = decodeAuction(auctionTuple);
|
|
4766
4792
|
const windowEndTimestamp = windowEnd;
|
|
4767
4793
|
const currentTimestamp = latestBlock.timestamp;
|
|
4794
|
+
const execution = deriveAuctionExecutionState({
|
|
4795
|
+
day: c.args.day,
|
|
4796
|
+
slot: c.args.slot,
|
|
4797
|
+
currentAuctionDay,
|
|
4798
|
+
currentAuctionSlot,
|
|
4799
|
+
settled: auction.settled,
|
|
4800
|
+
windowEnd: windowEndTimestamp,
|
|
4801
|
+
currentTimestamp
|
|
4802
|
+
});
|
|
4768
4803
|
return c.ok({
|
|
4769
4804
|
day: c.args.day,
|
|
4770
4805
|
slot: c.args.slot,
|
|
@@ -4773,11 +4808,9 @@ council.command("auction", {
|
|
|
4773
4808
|
settled: auction.settled,
|
|
4774
4809
|
windowEnd: timeValue(windowEndTimestamp, c.format),
|
|
4775
4810
|
windowEndRelative: relTime(windowEndTimestamp),
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
currentTimestamp
|
|
4780
|
-
})
|
|
4811
|
+
bidExecutableNow: execution.bidExecutableNow,
|
|
4812
|
+
executionStatus: execution.executionStatus,
|
|
4813
|
+
status: execution.executionStatus
|
|
4781
4814
|
});
|
|
4782
4815
|
}
|
|
4783
4816
|
});
|
|
@@ -4944,7 +4977,7 @@ council.command("bid", {
|
|
|
4944
4977
|
retryable: false
|
|
4945
4978
|
});
|
|
4946
4979
|
}
|
|
4947
|
-
const [auctionTuple, windowEnd, latestBlock] = await Promise.all([
|
|
4980
|
+
const [auctionTuple, windowEnd, latestBlock, currentAuctionDay, currentAuctionSlot] = await Promise.all([
|
|
4948
4981
|
client.readContract({
|
|
4949
4982
|
abi: councilSeatsAbi,
|
|
4950
4983
|
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
@@ -4957,24 +4990,45 @@ council.command("bid", {
|
|
|
4957
4990
|
functionName: "auctionWindowEnd",
|
|
4958
4991
|
args: [BigInt(c.args.day), c.args.slot]
|
|
4959
4992
|
}),
|
|
4960
|
-
client.getBlock({ blockTag: "latest" })
|
|
4993
|
+
client.getBlock({ blockTag: "latest" }),
|
|
4994
|
+
client.readContract({
|
|
4995
|
+
abi: councilSeatsAbi,
|
|
4996
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
4997
|
+
functionName: "currentAuctionDay"
|
|
4998
|
+
}),
|
|
4999
|
+
client.readContract({
|
|
5000
|
+
abi: councilSeatsAbi,
|
|
5001
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
5002
|
+
functionName: "currentAuctionSlot"
|
|
5003
|
+
})
|
|
4961
5004
|
]);
|
|
4962
5005
|
const auction = decodeAuction(auctionTuple);
|
|
4963
5006
|
const windowEndTimestamp = windowEnd;
|
|
4964
5007
|
const currentTimestamp = latestBlock.timestamp;
|
|
4965
|
-
const
|
|
5008
|
+
const execution = deriveAuctionExecutionState({
|
|
5009
|
+
day: c.args.day,
|
|
5010
|
+
slot: c.args.slot,
|
|
5011
|
+
currentAuctionDay,
|
|
5012
|
+
currentAuctionSlot,
|
|
4966
5013
|
settled: auction.settled,
|
|
4967
5014
|
windowEnd: windowEndTimestamp,
|
|
4968
5015
|
currentTimestamp
|
|
4969
5016
|
});
|
|
4970
|
-
if (
|
|
5017
|
+
if (execution.executionStatus === "settled") {
|
|
4971
5018
|
return c.error({
|
|
4972
5019
|
code: "AUCTION_SETTLED",
|
|
4973
5020
|
message: `Auction (day=${c.args.day}, slot=${c.args.slot}) is already settled. Winner: ${toChecksum(auction.highestBidder)}.`,
|
|
4974
5021
|
retryable: false
|
|
4975
5022
|
});
|
|
4976
5023
|
}
|
|
4977
|
-
if (
|
|
5024
|
+
if (execution.executionStatus === "upcoming") {
|
|
5025
|
+
return c.error({
|
|
5026
|
+
code: "AUCTION_NOT_ACTIVE",
|
|
5027
|
+
message: `Auction (day=${c.args.day}, slot=${c.args.slot}) is not the currently active slot. Current bid-executable slot is day=${Number(currentAuctionDay)}, slot=${Number(currentAuctionSlot)}.`,
|
|
5028
|
+
retryable: false
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
5031
|
+
if (execution.executionStatus === "closed_unsettled") {
|
|
4978
5032
|
return c.error({
|
|
4979
5033
|
code: "AUCTION_CLOSED",
|
|
4980
5034
|
message: `Auction (day=${c.args.day}, slot=${c.args.slot}) bidding window has ended. Use "council settle" instead.`,
|
|
@@ -5055,7 +5109,7 @@ council.command("settle", {
|
|
|
5055
5109
|
retryable: false
|
|
5056
5110
|
});
|
|
5057
5111
|
}
|
|
5058
|
-
const [auctionTuple, windowEnd, latestBlock] = await Promise.all([
|
|
5112
|
+
const [auctionTuple, windowEnd, latestBlock, currentAuctionDay, currentAuctionSlot] = await Promise.all([
|
|
5059
5113
|
client.readContract({
|
|
5060
5114
|
abi: councilSeatsAbi,
|
|
5061
5115
|
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
@@ -5068,30 +5122,51 @@ council.command("settle", {
|
|
|
5068
5122
|
functionName: "auctionWindowEnd",
|
|
5069
5123
|
args: [BigInt(c.args.day), c.args.slot]
|
|
5070
5124
|
}),
|
|
5071
|
-
client.getBlock({ blockTag: "latest" })
|
|
5125
|
+
client.getBlock({ blockTag: "latest" }),
|
|
5126
|
+
client.readContract({
|
|
5127
|
+
abi: councilSeatsAbi,
|
|
5128
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
5129
|
+
functionName: "currentAuctionDay"
|
|
5130
|
+
}),
|
|
5131
|
+
client.readContract({
|
|
5132
|
+
abi: councilSeatsAbi,
|
|
5133
|
+
address: ABSTRACT_MAINNET_ADDRESSES.councilSeats,
|
|
5134
|
+
functionName: "currentAuctionSlot"
|
|
5135
|
+
})
|
|
5072
5136
|
]);
|
|
5073
5137
|
const auction = decodeAuction(auctionTuple);
|
|
5074
5138
|
const windowEndTimestamp = windowEnd;
|
|
5075
5139
|
const currentTimestamp = latestBlock.timestamp;
|
|
5076
|
-
const
|
|
5140
|
+
const execution = deriveAuctionExecutionState({
|
|
5141
|
+
day: c.args.day,
|
|
5142
|
+
slot: c.args.slot,
|
|
5143
|
+
currentAuctionDay,
|
|
5144
|
+
currentAuctionSlot,
|
|
5077
5145
|
settled: auction.settled,
|
|
5078
5146
|
windowEnd: windowEndTimestamp,
|
|
5079
5147
|
currentTimestamp
|
|
5080
5148
|
});
|
|
5081
|
-
if (
|
|
5149
|
+
if (execution.executionStatus === "settled") {
|
|
5082
5150
|
return c.error({
|
|
5083
5151
|
code: "ALREADY_SETTLED",
|
|
5084
5152
|
message: `Auction (day=${c.args.day}, slot=${c.args.slot}) is already settled.`,
|
|
5085
5153
|
retryable: false
|
|
5086
5154
|
});
|
|
5087
5155
|
}
|
|
5088
|
-
if (
|
|
5156
|
+
if (execution.executionStatus === "open_now") {
|
|
5089
5157
|
return c.error({
|
|
5090
5158
|
code: "AUCTION_STILL_ACTIVE",
|
|
5091
5159
|
message: `Auction (day=${c.args.day}, slot=${c.args.slot}) is still accepting bids. Window ends ${relTime(windowEndTimestamp)}.`,
|
|
5092
5160
|
retryable: false
|
|
5093
5161
|
});
|
|
5094
5162
|
}
|
|
5163
|
+
if (execution.executionStatus === "upcoming") {
|
|
5164
|
+
return c.error({
|
|
5165
|
+
code: "AUCTION_STILL_ACTIVE",
|
|
5166
|
+
message: `Auction (day=${c.args.day}, slot=${c.args.slot}) is not yet the active slot. Current slot is day=${Number(currentAuctionDay)}, slot=${Number(currentAuctionSlot)}.`,
|
|
5167
|
+
retryable: false
|
|
5168
|
+
});
|
|
5169
|
+
}
|
|
5095
5170
|
const txResult = await assemblyWriteTx({
|
|
5096
5171
|
env: c.env,
|
|
5097
5172
|
options: c.options,
|
|
@@ -5872,12 +5947,21 @@ import { Cli as Cli4, z as z5 } from "incur";
|
|
|
5872
5947
|
var DEFAULT_MEMBER_SNAPSHOT_URL = "https://www.theaiassembly.org/api/indexer/members";
|
|
5873
5948
|
var REGISTERED_EVENT_SCAN_STEP = 100000n;
|
|
5874
5949
|
var REGISTERED_EVENT_SCAN_TIMEOUT_MS = 2e4;
|
|
5950
|
+
var MAX_MEMBER_LOOKUP_SUGGESTIONS = 5;
|
|
5875
5951
|
var env4 = z5.object({
|
|
5876
5952
|
ABSTRACT_RPC_URL: z5.string().optional().describe("Abstract RPC URL override"),
|
|
5877
5953
|
ASSEMBLY_INDEXER_URL: z5.string().optional().describe("Optional members snapshot endpoint (default: theaiassembly.org indexer)")
|
|
5878
5954
|
});
|
|
5879
5955
|
var timestampOutput4 = z5.union([z5.number(), z5.string()]);
|
|
5880
|
-
var
|
|
5956
|
+
var memberSnapshotEntrySchema = z5.union([
|
|
5957
|
+
z5.string(),
|
|
5958
|
+
z5.object({
|
|
5959
|
+
address: z5.string(),
|
|
5960
|
+
ens: z5.string().optional(),
|
|
5961
|
+
name: z5.string().optional()
|
|
5962
|
+
})
|
|
5963
|
+
]);
|
|
5964
|
+
var memberSnapshotSchema = z5.array(memberSnapshotEntrySchema);
|
|
5881
5965
|
var AssemblyApiValidationError = class extends Error {
|
|
5882
5966
|
constructor(details) {
|
|
5883
5967
|
super("Assembly API response validation failed");
|
|
@@ -5892,6 +5976,45 @@ var AssemblyIndexerUnavailableError = class extends Error {
|
|
|
5892
5976
|
this.name = "AssemblyIndexerUnavailableError";
|
|
5893
5977
|
}
|
|
5894
5978
|
};
|
|
5979
|
+
function mergeMemberIdentities(entries) {
|
|
5980
|
+
const byAddress = /* @__PURE__ */ new Map();
|
|
5981
|
+
for (const entry of entries) {
|
|
5982
|
+
const key = entry.address.toLowerCase();
|
|
5983
|
+
const existing = byAddress.get(key);
|
|
5984
|
+
if (!existing) {
|
|
5985
|
+
byAddress.set(key, entry);
|
|
5986
|
+
continue;
|
|
5987
|
+
}
|
|
5988
|
+
const merged = { address: existing.address };
|
|
5989
|
+
const ens = existing.ens ?? entry.ens;
|
|
5990
|
+
const name = existing.name ?? entry.name;
|
|
5991
|
+
if (ens !== void 0) merged.ens = ens;
|
|
5992
|
+
if (name !== void 0) merged.name = name;
|
|
5993
|
+
byAddress.set(key, merged);
|
|
5994
|
+
}
|
|
5995
|
+
return [...byAddress.values()];
|
|
5996
|
+
}
|
|
5997
|
+
function memberSnapshotEntryToIdentity(entry) {
|
|
5998
|
+
if (typeof entry === "string") return { address: entry };
|
|
5999
|
+
const identity = { address: entry.address };
|
|
6000
|
+
if (entry.ens !== void 0) identity.ens = entry.ens;
|
|
6001
|
+
if (entry.name !== void 0) identity.name = entry.name;
|
|
6002
|
+
return identity;
|
|
6003
|
+
}
|
|
6004
|
+
function matchableAddressInput(query) {
|
|
6005
|
+
return query.startsWith("0x") && query.length === 42;
|
|
6006
|
+
}
|
|
6007
|
+
function searchMemberIdentities(query, members2) {
|
|
6008
|
+
const needle = query.trim().toLowerCase();
|
|
6009
|
+
if (needle.length === 0) return [];
|
|
6010
|
+
const exactMatches = members2.filter((member) => {
|
|
6011
|
+
return member.address.toLowerCase() === needle || member.ens?.toLowerCase() === needle || member.name?.toLowerCase() === needle;
|
|
6012
|
+
});
|
|
6013
|
+
if (exactMatches.length > 0) return exactMatches;
|
|
6014
|
+
return members2.filter((member) => {
|
|
6015
|
+
return member.address.toLowerCase().includes(needle) || member.ens?.toLowerCase().includes(needle) || member.name?.toLowerCase().includes(needle);
|
|
6016
|
+
});
|
|
6017
|
+
}
|
|
5895
6018
|
async function memberSnapshot(url) {
|
|
5896
6019
|
let res;
|
|
5897
6020
|
try {
|
|
@@ -5914,7 +6037,7 @@ async function memberSnapshot(url) {
|
|
|
5914
6037
|
const json = await res.json();
|
|
5915
6038
|
const parsed = memberSnapshotSchema.safeParse(json);
|
|
5916
6039
|
if (parsed.success) {
|
|
5917
|
-
return parsed.data;
|
|
6040
|
+
return mergeMemberIdentities(parsed.data.map(memberSnapshotEntryToIdentity));
|
|
5918
6041
|
}
|
|
5919
6042
|
throw new AssemblyApiValidationError({
|
|
5920
6043
|
code: "INVALID_ASSEMBLY_API_RESPONSE",
|
|
@@ -5958,7 +6081,28 @@ async function membersFromRegisteredEvents(client) {
|
|
|
5958
6081
|
}
|
|
5959
6082
|
}
|
|
5960
6083
|
}
|
|
5961
|
-
return [...addresses];
|
|
6084
|
+
return [...addresses].map((address) => ({ address }));
|
|
6085
|
+
}
|
|
6086
|
+
async function loadMemberIdentities(client, snapshotUrl) {
|
|
6087
|
+
try {
|
|
6088
|
+
return { members: await memberSnapshot(snapshotUrl) };
|
|
6089
|
+
} catch (error) {
|
|
6090
|
+
if (error instanceof AssemblyApiValidationError) {
|
|
6091
|
+
throw error;
|
|
6092
|
+
}
|
|
6093
|
+
if (!(error instanceof AssemblyIndexerUnavailableError)) {
|
|
6094
|
+
throw error;
|
|
6095
|
+
}
|
|
6096
|
+
const fallbackMembers = await withTimeout(
|
|
6097
|
+
membersFromRegisteredEvents(client),
|
|
6098
|
+
REGISTERED_EVENT_SCAN_TIMEOUT_MS,
|
|
6099
|
+
`Registered event fallback scan timed out after ${REGISTERED_EVENT_SCAN_TIMEOUT_MS}ms`
|
|
6100
|
+
);
|
|
6101
|
+
return {
|
|
6102
|
+
members: mergeMemberIdentities(fallbackMembers),
|
|
6103
|
+
fallbackReason: error.details
|
|
6104
|
+
};
|
|
6105
|
+
}
|
|
5962
6106
|
}
|
|
5963
6107
|
function indexerIssue(details) {
|
|
5964
6108
|
if (typeof details.status === "number") {
|
|
@@ -5979,6 +6123,12 @@ function emitIndexerFallbackWarning(details) {
|
|
|
5979
6123
|
`
|
|
5980
6124
|
);
|
|
5981
6125
|
}
|
|
6126
|
+
function describeMember(member) {
|
|
6127
|
+
const parts = [toChecksum(member.address)];
|
|
6128
|
+
if (member.ens) parts.push(`ens=${member.ens}`);
|
|
6129
|
+
if (member.name) parts.push(`name=${member.name}`);
|
|
6130
|
+
return parts.join(" ");
|
|
6131
|
+
}
|
|
5982
6132
|
var members = Cli4.create("members", {
|
|
5983
6133
|
description: "Inspect Assembly membership and registry fee state."
|
|
5984
6134
|
});
|
|
@@ -6006,10 +6156,12 @@ members.command("list", {
|
|
|
6006
6156
|
async run(c) {
|
|
6007
6157
|
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
6008
6158
|
const snapshotUrl = c.env.ASSEMBLY_INDEXER_URL ?? DEFAULT_MEMBER_SNAPSHOT_URL;
|
|
6009
|
-
let
|
|
6159
|
+
let memberIdentities;
|
|
6010
6160
|
let fallbackReason;
|
|
6011
6161
|
try {
|
|
6012
|
-
|
|
6162
|
+
const loaded = await loadMemberIdentities(client, snapshotUrl);
|
|
6163
|
+
memberIdentities = loaded.members;
|
|
6164
|
+
fallbackReason = loaded.fallbackReason;
|
|
6013
6165
|
} catch (error) {
|
|
6014
6166
|
if (error instanceof AssemblyApiValidationError) {
|
|
6015
6167
|
return c.error({
|
|
@@ -6018,27 +6170,19 @@ members.command("list", {
|
|
|
6018
6170
|
retryable: false
|
|
6019
6171
|
});
|
|
6020
6172
|
}
|
|
6021
|
-
if (!(error instanceof
|
|
6173
|
+
if (!(error instanceof Error)) {
|
|
6022
6174
|
throw error;
|
|
6023
6175
|
}
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
`Registered event fallback scan timed out after ${REGISTERED_EVENT_SCAN_TIMEOUT_MS}ms`
|
|
6030
|
-
);
|
|
6031
|
-
} catch (fallbackError) {
|
|
6032
|
-
return c.error({
|
|
6033
|
-
code: "MEMBER_LIST_SOURCE_UNAVAILABLE",
|
|
6034
|
-
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)}`,
|
|
6035
|
-
retryable: true
|
|
6036
|
-
});
|
|
6037
|
-
}
|
|
6176
|
+
return c.error({
|
|
6177
|
+
code: "MEMBER_LIST_SOURCE_UNAVAILABLE",
|
|
6178
|
+
message: `Unable to load member list from indexer (${snapshotUrl}) or on-chain fallback: ${error.message}`,
|
|
6179
|
+
retryable: true
|
|
6180
|
+
});
|
|
6038
6181
|
}
|
|
6039
6182
|
if (fallbackReason) {
|
|
6040
6183
|
emitIndexerFallbackWarning(fallbackReason);
|
|
6041
6184
|
}
|
|
6185
|
+
const addresses = memberIdentities.map((member) => member.address);
|
|
6042
6186
|
const calls = addresses.flatMap((address) => [
|
|
6043
6187
|
{
|
|
6044
6188
|
abi: registryAbi,
|
|
@@ -6074,9 +6218,9 @@ members.command("list", {
|
|
|
6074
6218
|
}
|
|
6075
6219
|
});
|
|
6076
6220
|
members.command("info", {
|
|
6077
|
-
description: "Get registry record and active status
|
|
6221
|
+
description: "Get member registry record and active status by full address, partial address, ENS, or name.",
|
|
6078
6222
|
args: z5.object({
|
|
6079
|
-
address: z5.string().describe("Member
|
|
6223
|
+
address: z5.string().describe("Member lookup query (full/partial address, ENS, or name metadata)")
|
|
6080
6224
|
}),
|
|
6081
6225
|
env: env4,
|
|
6082
6226
|
output: z5.object({
|
|
@@ -6091,26 +6235,74 @@ members.command("info", {
|
|
|
6091
6235
|
{
|
|
6092
6236
|
args: { address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
|
|
6093
6237
|
description: "Inspect one member address"
|
|
6238
|
+
},
|
|
6239
|
+
{
|
|
6240
|
+
args: { address: "a96045" },
|
|
6241
|
+
description: "Lookup a member by partial address"
|
|
6094
6242
|
}
|
|
6095
6243
|
],
|
|
6096
6244
|
async run(c) {
|
|
6097
6245
|
const client = createAssemblyPublicClient(c.env.ABSTRACT_RPC_URL);
|
|
6246
|
+
const snapshotUrl = c.env.ASSEMBLY_INDEXER_URL ?? DEFAULT_MEMBER_SNAPSHOT_URL;
|
|
6247
|
+
let lookupAddress = c.args.address;
|
|
6248
|
+
if (!matchableAddressInput(c.args.address)) {
|
|
6249
|
+
let loadedMembers;
|
|
6250
|
+
let fallbackReason;
|
|
6251
|
+
try {
|
|
6252
|
+
const loaded = await loadMemberIdentities(client, snapshotUrl);
|
|
6253
|
+
loadedMembers = loaded.members;
|
|
6254
|
+
fallbackReason = loaded.fallbackReason;
|
|
6255
|
+
} catch (error) {
|
|
6256
|
+
if (error instanceof AssemblyApiValidationError) {
|
|
6257
|
+
return c.error({
|
|
6258
|
+
code: error.details.code,
|
|
6259
|
+
message: `Member snapshot response failed validation. url=${error.details.url}; issues=${JSON.stringify(error.details.issues)}; response=${JSON.stringify(error.details.response)}`,
|
|
6260
|
+
retryable: false
|
|
6261
|
+
});
|
|
6262
|
+
}
|
|
6263
|
+
return c.error({
|
|
6264
|
+
code: "MEMBER_LOOKUP_SOURCE_UNAVAILABLE",
|
|
6265
|
+
message: error instanceof Error ? `Unable to resolve member query from indexer (${snapshotUrl}) or on-chain fallback: ${error.message}` : `Unable to resolve member query from indexer (${snapshotUrl}) or on-chain fallback.`,
|
|
6266
|
+
retryable: true
|
|
6267
|
+
});
|
|
6268
|
+
}
|
|
6269
|
+
if (fallbackReason) {
|
|
6270
|
+
emitIndexerFallbackWarning(fallbackReason);
|
|
6271
|
+
}
|
|
6272
|
+
const matches = searchMemberIdentities(c.args.address, loadedMembers);
|
|
6273
|
+
if (matches.length === 0) {
|
|
6274
|
+
return c.error({
|
|
6275
|
+
code: "MEMBER_NOT_FOUND",
|
|
6276
|
+
message: `No Assembly member matched "${c.args.address}". Try a longer query or run \`assembly members list\` first.`,
|
|
6277
|
+
retryable: false
|
|
6278
|
+
});
|
|
6279
|
+
}
|
|
6280
|
+
if (matches.length > 1) {
|
|
6281
|
+
const suggestions = matches.slice(0, MAX_MEMBER_LOOKUP_SUGGESTIONS).map(describeMember).join("; ");
|
|
6282
|
+
return c.error({
|
|
6283
|
+
code: "AMBIGUOUS_MEMBER_QUERY",
|
|
6284
|
+
message: `Member query "${c.args.address}" matched ${matches.length} members. Be more specific. Matches: ${suggestions}`,
|
|
6285
|
+
retryable: false
|
|
6286
|
+
});
|
|
6287
|
+
}
|
|
6288
|
+
lookupAddress = matches[0].address;
|
|
6289
|
+
}
|
|
6098
6290
|
const [member, active] = await Promise.all([
|
|
6099
6291
|
client.readContract({
|
|
6100
6292
|
abi: registryAbi,
|
|
6101
6293
|
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
6102
6294
|
functionName: "members",
|
|
6103
|
-
args: [
|
|
6295
|
+
args: [lookupAddress]
|
|
6104
6296
|
}),
|
|
6105
6297
|
client.readContract({
|
|
6106
6298
|
abi: registryAbi,
|
|
6107
6299
|
address: ABSTRACT_MAINNET_ADDRESSES.registry,
|
|
6108
6300
|
functionName: "isActive",
|
|
6109
|
-
args: [
|
|
6301
|
+
args: [lookupAddress]
|
|
6110
6302
|
})
|
|
6111
6303
|
]);
|
|
6112
6304
|
return c.ok({
|
|
6113
|
-
address: toChecksum(
|
|
6305
|
+
address: toChecksum(lookupAddress),
|
|
6114
6306
|
active,
|
|
6115
6307
|
activeUntil: timeValue(member.activeUntil, c.format),
|
|
6116
6308
|
lastHeartbeatAt: timeValue(member.lastHeartbeatAt, c.format),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spectratools/assembly-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "CLI for Assembly governance on Abstract (members, council, forum, proposals, and treasury).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"incur": "^0.3.0",
|
|
33
33
|
"ox": "^0.14.0",
|
|
34
34
|
"viem": "^2.47.0",
|
|
35
|
-
"@spectratools/
|
|
36
|
-
"@spectratools/
|
|
35
|
+
"@spectratools/cli-shared": "0.1.1",
|
|
36
|
+
"@spectratools/tx-shared": "0.4.2"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"typescript": "5.7.3",
|