@poa-box/core 0.1.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/README.md +201 -0
- package/dist/abis/DirectDemocracyVotingNew.d.ts +749 -0
- package/dist/abis/DirectDemocracyVotingNew.js +961 -0
- package/dist/abis/ERC20.d.ts +90 -0
- package/dist/abis/ERC20.js +125 -0
- package/dist/abis/EducationHubNew.d.ts +590 -0
- package/dist/abis/EducationHubNew.js +762 -0
- package/dist/abis/EligibilityModuleNew.d.ts +1615 -0
- package/dist/abis/EligibilityModuleNew.js +2082 -0
- package/dist/abis/Executor.d.ts +619 -0
- package/dist/abis/Executor.js +796 -0
- package/dist/abis/HybridVotingNew.d.ts +898 -0
- package/dist/abis/HybridVotingNew.js +1149 -0
- package/dist/abis/ImplementationRegistry.d.ts +270 -0
- package/dist/abis/ImplementationRegistry.js +355 -0
- package/dist/abis/OrgDeployerNew.d.ts +1275 -0
- package/dist/abis/OrgDeployerNew.js +1630 -0
- package/dist/abis/OrgRegistry.d.ts +670 -0
- package/dist/abis/OrgRegistry.js +868 -0
- package/dist/abis/ParticipationToken.d.ts +1086 -0
- package/dist/abis/ParticipationToken.js +1415 -0
- package/dist/abis/PasskeyAccount.d.ts +788 -0
- package/dist/abis/PasskeyAccount.js +1019 -0
- package/dist/abis/PasskeyAccountFactory.d.ts +344 -0
- package/dist/abis/PasskeyAccountFactory.js +449 -0
- package/dist/abis/PaymasterHub.d.ts +1603 -0
- package/dist/abis/PaymasterHub.js +2048 -0
- package/dist/abis/PaymentManager.d.ts +520 -0
- package/dist/abis/PaymentManager.js +671 -0
- package/dist/abis/PoaManager.d.ts +344 -0
- package/dist/abis/PoaManager.js +449 -0
- package/dist/abis/QuickJoinNew.d.ts +855 -0
- package/dist/abis/QuickJoinNew.js +1098 -0
- package/dist/abis/TaskManagerNew.d.ts +1236 -0
- package/dist/abis/TaskManagerNew.js +1573 -0
- package/dist/abis/ToggleModule.d.ts +193 -0
- package/dist/abis/ToggleModule.js +255 -0
- package/dist/abis/UniversalAccountRegistry.d.ts +577 -0
- package/dist/abis/UniversalAccountRegistry.js +750 -0
- package/dist/abis/ZkEmailInvites.d.ts +823 -0
- package/dist/abis/ZkEmailInvites.js +1060 -0
- package/dist/abis/external/AaveGovernanceV2.d.ts +128 -0
- package/dist/abis/external/AaveGovernanceV2.js +177 -0
- package/dist/abis/external/AaveGovernanceV3.d.ts +160 -0
- package/dist/abis/external/AaveGovernanceV3.js +220 -0
- package/dist/abis/external/AragonVoting.d.ts +107 -0
- package/dist/abis/external/AragonVoting.js +150 -0
- package/dist/abis/external/CurveGaugeController.d.ts +95 -0
- package/dist/abis/external/CurveGaugeController.js +130 -0
- package/dist/abis/external/CurveVotingEscrow.d.ts +86 -0
- package/dist/abis/external/CurveVotingEscrow.js +116 -0
- package/dist/abis/external/GovernorAlpha.d.ts +172 -0
- package/dist/abis/external/GovernorAlpha.js +228 -0
- package/dist/abis/external/MakerDAOChief.d.ts +92 -0
- package/dist/abis/external/MakerDAOChief.js +129 -0
- package/dist/abis/external/OZGovernor.d.ts +227 -0
- package/dist/abis/external/OZGovernor.js +317 -0
- package/dist/abis/external/SolidlyVotingEscrow.d.ts +196 -0
- package/dist/abis/external/SolidlyVotingEscrow.js +260 -0
- package/dist/abis/index.d.ts +62 -0
- package/dist/abis/index.js +98 -0
- package/dist/chains.d.ts +150 -0
- package/dist/chains.js +320 -0
- package/dist/context.d.ts +48 -0
- package/dist/context.js +38 -0
- package/dist/contracts.d.ts +24 -0
- package/dist/contracts.js +41 -0
- package/dist/encoding.d.ts +58 -0
- package/dist/encoding.js +243 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +4 -0
- package/dist/error-catalog.d.ts +41 -0
- package/dist/error-catalog.js +941 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +60 -0
- package/dist/execute/ethers.d.ts +93 -0
- package/dist/execute/ethers.js +323 -0
- package/dist/execute/index.d.ts +7 -0
- package/dist/execute/index.js +23 -0
- package/dist/execute/sponsored.d.ts +68 -0
- package/dist/execute/sponsored.js +277 -0
- package/dist/exit-codes.d.ts +18 -0
- package/dist/exit-codes.js +21 -0
- package/dist/format.d.ts +21 -0
- package/dist/format.js +89 -0
- package/dist/graph/client.d.ts +298 -0
- package/dist/graph/client.js +702 -0
- package/dist/graph/documents/activity.d.ts +9 -0
- package/dist/graph/documents/activity.js +164 -0
- package/dist/graph/documents/beacons.d.ts +172 -0
- package/dist/graph/documents/beacons.js +345 -0
- package/dist/graph/documents/index.d.ts +21 -0
- package/dist/graph/documents/index.js +57 -0
- package/dist/graph/documents/infrastructure.d.ts +30 -0
- package/dist/graph/documents/infrastructure.js +34 -0
- package/dist/graph/documents/org.d.ts +15 -0
- package/dist/graph/documents/org.js +249 -0
- package/dist/graph/documents/paymaster.d.ts +95 -0
- package/dist/graph/documents/paymaster.js +101 -0
- package/dist/graph/documents/role.d.ts +6 -0
- package/dist/graph/documents/role.js +41 -0
- package/dist/graph/documents/roles.d.ts +50 -0
- package/dist/graph/documents/roles.js +112 -0
- package/dist/graph/documents/task.d.ts +84 -0
- package/dist/graph/documents/task.js +236 -0
- package/dist/graph/documents/token.d.ts +13 -0
- package/dist/graph/documents/token.js +126 -0
- package/dist/graph/documents/treasury.d.ts +39 -0
- package/dist/graph/documents/treasury.js +136 -0
- package/dist/graph/documents/user.d.ts +120 -0
- package/dist/graph/documents/user.js +302 -0
- package/dist/graph/documents/voting-classes.d.ts +69 -0
- package/dist/graph/documents/voting-classes.js +149 -0
- package/dist/graph/documents/voting.d.ts +28 -0
- package/dist/graph/documents/voting.js +163 -0
- package/dist/graph/documents/vouch.d.ts +79 -0
- package/dist/graph/documents/vouch.js +171 -0
- package/dist/graph/documents/zkemail.d.ts +63 -0
- package/dist/graph/documents/zkemail.js +188 -0
- package/dist/graph/index.d.ts +2 -0
- package/dist/graph/index.js +41 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +92 -0
- package/dist/ipfs.d.ts +39 -0
- package/dist/ipfs.js +214 -0
- package/dist/label-aliases.d.ts +31 -0
- package/dist/label-aliases.js +70 -0
- package/dist/metadata/education.d.ts +30 -0
- package/dist/metadata/education.js +31 -0
- package/dist/metadata/index.d.ts +12 -0
- package/dist/metadata/index.js +48 -0
- package/dist/metadata/org.d.ts +92 -0
- package/dist/metadata/org.js +93 -0
- package/dist/metadata/proposal.d.ts +44 -0
- package/dist/metadata/proposal.js +45 -0
- package/dist/metadata/role.d.ts +38 -0
- package/dist/metadata/role.js +35 -0
- package/dist/metadata/task.d.ts +108 -0
- package/dist/metadata/task.js +81 -0
- package/dist/metadata/token.d.ts +32 -0
- package/dist/metadata/token.js +36 -0
- package/dist/metadata/user.d.ts +63 -0
- package/dist/metadata/user.js +70 -0
- package/dist/multicall.d.ts +32 -0
- package/dist/multicall.js +96 -0
- package/dist/payout.d.ts +59 -0
- package/dist/payout.js +96 -0
- package/dist/perms.d.ts +47 -0
- package/dist/perms.js +120 -0
- package/dist/preflight.d.ts +72 -0
- package/dist/preflight.js +269 -0
- package/dist/reads/education.d.ts +79 -0
- package/dist/reads/education.js +122 -0
- package/dist/reads/eligibility.d.ts +377 -0
- package/dist/reads/eligibility.js +687 -0
- package/dist/reads/index.d.ts +16 -0
- package/dist/reads/index.js +55 -0
- package/dist/reads/org.d.ts +258 -0
- package/dist/reads/org.js +220 -0
- package/dist/reads/paymaster.d.ts +147 -0
- package/dist/reads/paymaster.js +235 -0
- package/dist/reads/project.d.ts +33 -0
- package/dist/reads/project.js +62 -0
- package/dist/reads/resolve.d.ts +34 -0
- package/dist/reads/resolve.js +64 -0
- package/dist/reads/task.d.ts +286 -0
- package/dist/reads/task.js +280 -0
- package/dist/reads/token.d.ts +94 -0
- package/dist/reads/token.js +75 -0
- package/dist/reads/treasury.d.ts +222 -0
- package/dist/reads/treasury.js +241 -0
- package/dist/reads/user.d.ts +229 -0
- package/dist/reads/user.js +155 -0
- package/dist/reads/vote.d.ts +296 -0
- package/dist/reads/vote.js +392 -0
- package/dist/reads/zkemail.d.ts +117 -0
- package/dist/reads/zkemail.js +217 -0
- package/dist/similarity.d.ts +36 -0
- package/dist/similarity.js +67 -0
- package/dist/sponsorship-config.d.ts +47 -0
- package/dist/sponsorship-config.js +41 -0
- package/dist/stats.d.ts +11 -0
- package/dist/stats.js +27 -0
- package/dist/task-lens.d.ts +113 -0
- package/dist/task-lens.js +210 -0
- package/dist/tx/education.d.ts +169 -0
- package/dist/tx/education.js +344 -0
- package/dist/tx/eligibility.d.ts +511 -0
- package/dist/tx/eligibility.js +818 -0
- package/dist/tx/governance.d.ts +85 -0
- package/dist/tx/governance.js +87 -0
- package/dist/tx/index.d.ts +17 -0
- package/dist/tx/index.js +56 -0
- package/dist/tx/intent.d.ts +67 -0
- package/dist/tx/intent.js +48 -0
- package/dist/tx/org.d.ts +321 -0
- package/dist/tx/org.js +544 -0
- package/dist/tx/paymaster.d.ts +140 -0
- package/dist/tx/paymaster.js +251 -0
- package/dist/tx/project.d.ts +146 -0
- package/dist/tx/project.js +217 -0
- package/dist/tx/task.d.ts +480 -0
- package/dist/tx/task.js +1149 -0
- package/dist/tx/token.d.ts +96 -0
- package/dist/tx/token.js +165 -0
- package/dist/tx/treasury.d.ts +474 -0
- package/dist/tx/treasury.js +911 -0
- package/dist/tx/user.d.ts +177 -0
- package/dist/tx/user.js +292 -0
- package/dist/tx/vote.d.ts +332 -0
- package/dist/tx/vote.js +762 -0
- package/dist/tx/zkemail.d.ts +90 -0
- package/dist/tx/zkemail.js +210 -0
- package/dist/validation.d.ts +8 -0
- package/dist/validation.js +39 -0
- package/dist/version.d.ts +142 -0
- package/dist/version.js +257 -0
- package/dist/zkemail.d.ts +161 -0
- package/dist/zkemail.js +299 -0
- package/package.json +130 -0
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared EligibilityModule read layer for the vouch + role domains.
|
|
4
|
+
* Port of src/commands/vouch/helpers.ts (plus the subgraph list reads from
|
|
5
|
+
* src/commands/vouch/list.ts and src/commands/role/applications.ts): functions
|
|
6
|
+
* take the GraphClient / provider explicitly instead of importing
|
|
7
|
+
* process-global singletons.
|
|
8
|
+
*
|
|
9
|
+
* Both domains read the same module, whose admin surface is superAdmin-gated.
|
|
10
|
+
* Facts VERIFIED against contracts origin/main src/EligibilityModule.sol:
|
|
11
|
+
*
|
|
12
|
+
* - `superAdmin()` public getter exists; every eligibility/hat-admin write is
|
|
13
|
+
* `onlySuperAdmin` (setWearerEligibility, setDefaultEligibility,
|
|
14
|
+
* clearWearerEligibility, batchSetWearerEligibility, setDefaultEligibility,
|
|
15
|
+
* createHatWithEligibility, mintHatToAddress, batchMintHats, pause/unpause,
|
|
16
|
+
* transferSuperAdmin, setUserJoinTime, setUserJoinTimeNow, configureVouching,
|
|
17
|
+
* resetVouches, clearWearerVouches).
|
|
18
|
+
* - VouchConfig struct: (uint32 quorum, uint256 membershipHatId, uint8 flags)
|
|
19
|
+
* with flags bit 0 = enabled (ENABLED_FLAG 0x01) and bit 1 =
|
|
20
|
+
* combineWithHierarchy (COMBINE_HIERARCHY_FLAG 0x02). Getter: getVouchConfig.
|
|
21
|
+
* - Rate limiting: getMaxDailyVouches() (DEFAULT_MAX_DAILY_VOUCHES = 20 when
|
|
22
|
+
* unset), getCurrentDailyVouchCount(user) keyed by UTC day
|
|
23
|
+
* (block.timestamp / 86400), getUserJoinTime(user), and the authoritative
|
|
24
|
+
* combined answer canUserVouch(user).
|
|
25
|
+
* - vouchFor does NOT auto-mint at quorum: reaching quorum makes
|
|
26
|
+
* getWearerStatus() return eligible, and the wearer claims explicitly via
|
|
27
|
+
* claimVouchedHat (claim-based pattern per the contract doc comment).
|
|
28
|
+
*
|
|
29
|
+
* READ SOURCING (subgraph vs RPC), settled 2026-07-30 against the live
|
|
30
|
+
* poa-gnosis-v-1 / poa-arb-v-1 deployments:
|
|
31
|
+
*
|
|
32
|
+
* - DISPLAY reads (pop vouch status, pop vouch config show) go SUBGRAPH-FIRST
|
|
33
|
+
* with the RPC getter kept as fallback: VouchConfig.{quorum,membershipHatId,
|
|
34
|
+
* enabled,combinesWithHierarchy} and a count of active Vouch rows are
|
|
35
|
+
* verified-populated and byte-for-byte equal to the on-chain values.
|
|
36
|
+
* - VOUCH COUNTS ARE EPOCH-SCOPED. configureVouching/batchConfigureVouching/
|
|
37
|
+
* resetVouches bump vouchConfigEpoch[hatId] and clearWearerVouches parks
|
|
38
|
+
* wearerVouchEpoch on a 2^256-1 sentinel; currentVouchCount() returns 0 when
|
|
39
|
+
* the two disagree, and NO event marks the individual vouches dead. Counting
|
|
40
|
+
* active Vouch rows therefore overcounts after any reconfiguration and would
|
|
41
|
+
* report quorum met against a contract that says zero. Newer deployments
|
|
42
|
+
* index the epochs (WearerVouchState), which is exact; older ones can only
|
|
43
|
+
* detect the ambiguity and refuse. See graph/documents/vouch.ts for both tiers.
|
|
44
|
+
* - WRITE PRE-FLIGHT reads stay on RPC. superAdmin(), paused(),
|
|
45
|
+
* hasVouched(), hasActiveApplication(), getWearerStatus(), getDefaultRules()
|
|
46
|
+
* and the vouch-for/claim gates all exist to PREDICT A REVERT; indexing lag
|
|
47
|
+
* there either broadcasts a doomed transaction or blocks a valid one.
|
|
48
|
+
* They are instead batched through Multicall3 so each pre-flight costs ONE
|
|
49
|
+
* round-trip instead of N.
|
|
50
|
+
* - NO subgraph field exists for getMaxDailyVouches(),
|
|
51
|
+
* getCurrentDailyVouchCount() or canUserVouch(); UserJoinTime has ZERO rows
|
|
52
|
+
* on the live Gnosis deployment. The whole VoucherGate therefore stays RPC.
|
|
53
|
+
*/
|
|
54
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
|
+
exports.VOUCH_FLAG_COMBINE_HIERARCHY = exports.VOUCH_FLAG_ENABLED = void 0;
|
|
56
|
+
exports.resolveEligibilityModule = resolveEligibilityModule;
|
|
57
|
+
exports.parseHatId = parseHatId;
|
|
58
|
+
exports.decodeVouchConfig = decodeVouchConfig;
|
|
59
|
+
exports.batchEligibilityReadsSettled = batchEligibilityReadsSettled;
|
|
60
|
+
exports.batchEligibilityReads = batchEligibilityReads;
|
|
61
|
+
exports.fetchVouchConfigFromSubgraph = fetchVouchConfigFromSubgraph;
|
|
62
|
+
exports.readVouchConfigStatus = readVouchConfigStatus;
|
|
63
|
+
exports.fetchWearerVouchStateFromSubgraph = fetchWearerVouchStateFromSubgraph;
|
|
64
|
+
exports.readWearerVouchStateOnchain = readWearerVouchStateOnchain;
|
|
65
|
+
exports.readWearerVouchState = readWearerVouchState;
|
|
66
|
+
exports.getSuperAdmin = getSuperAdmin;
|
|
67
|
+
exports.requireSuperAdmin = requireSuperAdmin;
|
|
68
|
+
exports.requireSuperAdminWithRead = requireSuperAdminWithRead;
|
|
69
|
+
exports.readVoucherGate = readVoucherGate;
|
|
70
|
+
exports.vouchRestriction = vouchRestriction;
|
|
71
|
+
exports.vouchQuotaLabel = vouchQuotaLabel;
|
|
72
|
+
exports.readVouchPreflight = readVouchPreflight;
|
|
73
|
+
exports.gateVouch = gateVouch;
|
|
74
|
+
exports.readClaimGate = readClaimGate;
|
|
75
|
+
exports.hasVouched = hasVouched;
|
|
76
|
+
exports.readRevokeGate = readRevokeGate;
|
|
77
|
+
exports.hasActiveApplication = hasActiveApplication;
|
|
78
|
+
exports.isPaused = isPaused;
|
|
79
|
+
exports.listVouches = listVouches;
|
|
80
|
+
exports.listRoleApplications = listRoleApplications;
|
|
81
|
+
exports.resolveRoleNameToHatId = resolveRoleNameToHatId;
|
|
82
|
+
const ethers_1 = require("ethers");
|
|
83
|
+
const resolve_1 = require("./resolve");
|
|
84
|
+
const contracts_1 = require("../contracts");
|
|
85
|
+
const multicall_1 = require("../multicall");
|
|
86
|
+
const vouch_1 = require("../graph/documents/vouch");
|
|
87
|
+
const role_1 = require("../graph/documents/role");
|
|
88
|
+
const format_1 = require("../format");
|
|
89
|
+
const errors_1 = require("../errors");
|
|
90
|
+
const exit_codes_1 = require("../exit-codes");
|
|
91
|
+
const perms_1 = require("../perms");
|
|
92
|
+
Object.defineProperty(exports, "VOUCH_FLAG_ENABLED", { enumerable: true, get: function () { return perms_1.VOUCH_FLAG_ENABLED; } });
|
|
93
|
+
Object.defineProperty(exports, "VOUCH_FLAG_COMBINE_HIERARCHY", { enumerable: true, get: function () { return perms_1.VOUCH_FLAG_COMBINE_HIERARCHY; } });
|
|
94
|
+
/**
|
|
95
|
+
* Resolve an org to its EligibilityModule address.
|
|
96
|
+
* Port of src/commands/vouch/helpers.ts resolveEligibilityModule.
|
|
97
|
+
*/
|
|
98
|
+
async function resolveEligibilityModule(client, orgIdOrName, chainId) {
|
|
99
|
+
const modules = await (0, resolve_1.resolveOrgModules)(client, orgIdOrName, chainId);
|
|
100
|
+
return {
|
|
101
|
+
orgId: modules.orgId,
|
|
102
|
+
eligibilityModuleAddress: (0, resolve_1.requireModule)(modules, 'eligibilityModuleAddress'),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Parse a --hat argument (decimal or 0x-hex) into a BigNumber hat ID. */
|
|
106
|
+
function parseHatId(input) {
|
|
107
|
+
try {
|
|
108
|
+
return ethers_1.ethers.BigNumber.from(String(input).trim());
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
throw new errors_1.CliError(`Invalid --hat "${input}".`, exit_codes_1.EXIT.USAGE, 'Pass the hat ID as a decimal or 0x-hex integer (see pop org roles).');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Decode a raw getVouchConfig/vouchConfigs result into named fields. */
|
|
115
|
+
function decodeVouchConfig(raw) {
|
|
116
|
+
const flags = ethers_1.ethers.BigNumber.from(raw.flags).toNumber();
|
|
117
|
+
return {
|
|
118
|
+
quorum: ethers_1.ethers.BigNumber.from(raw.quorum).toNumber(),
|
|
119
|
+
membershipHatId: ethers_1.ethers.BigNumber.from(raw.membershipHatId).toString(),
|
|
120
|
+
enabled: (flags & perms_1.VOUCH_FLAG_ENABLED) !== 0,
|
|
121
|
+
combineWithHierarchy: (flags & perms_1.VOUCH_FLAG_COMBINE_HIERARCHY) !== 0,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Batch EligibilityModule view calls into ONE Multicall3 round-trip
|
|
126
|
+
* (multicall.ts degrades to parallel provider.call on chains without the
|
|
127
|
+
* canonical deployment, so callers always get one result per call).
|
|
128
|
+
*
|
|
129
|
+
* Every caller is a write pre-flight, so a sub-call that reverts must NEVER be
|
|
130
|
+
* silently decoded as a falsy answer: Multicall3.tryAggregate is invoked with
|
|
131
|
+
* requireSuccess=false, and any call that comes back unsuccessful is re-issued
|
|
132
|
+
* through the ethers contract so the original error propagates exactly as it
|
|
133
|
+
* did before batching.
|
|
134
|
+
*
|
|
135
|
+
* Return shape matches `contract.fn(...)`: a single output is unwrapped, a
|
|
136
|
+
* multi-output (getWearerStatus, getDefaultRules) stays an ethers Result with
|
|
137
|
+
* both named and positional access.
|
|
138
|
+
*/
|
|
139
|
+
async function batchEligibilityReadsSettled(provider, eligibilityModuleAddress, reads) {
|
|
140
|
+
const contract = (0, contracts_1.createReadContract)(eligibilityModuleAddress, 'EligibilityModuleNew', provider);
|
|
141
|
+
const iface = contract.interface;
|
|
142
|
+
let results = [];
|
|
143
|
+
try {
|
|
144
|
+
const calls = reads.map(r => ({
|
|
145
|
+
to: eligibilityModuleAddress,
|
|
146
|
+
data: iface.encodeFunctionData(r.fn, r.args),
|
|
147
|
+
}));
|
|
148
|
+
results = await (0, multicall_1.tryAggregate)(provider, calls);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
results = [];
|
|
152
|
+
}
|
|
153
|
+
return Promise.allSettled(reads.map(async (read, i) => {
|
|
154
|
+
const result = results[i];
|
|
155
|
+
if (!result || !result.success || !result.returnData || result.returnData === '0x') {
|
|
156
|
+
// Not batchable / reverted — re-issue so the natural error surfaces.
|
|
157
|
+
return contract[read.fn](...read.args);
|
|
158
|
+
}
|
|
159
|
+
const decoded = iface.decodeFunctionResult(read.fn, result.returnData);
|
|
160
|
+
return decoded.length === 1 ? decoded[0] : decoded;
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
/** batchEligibilityReadsSettled, rethrowing the first failure (Promise.all semantics). */
|
|
164
|
+
async function batchEligibilityReads(provider, eligibilityModuleAddress, reads) {
|
|
165
|
+
const settled = await batchEligibilityReadsSettled(provider, eligibilityModuleAddress, reads);
|
|
166
|
+
return settled.map((entry) => {
|
|
167
|
+
if (entry.status === 'rejected')
|
|
168
|
+
throw entry.reason;
|
|
169
|
+
return entry.value;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Subgraph VouchConfig row → the same VouchConfigView the RPC tuple decodes to.
|
|
174
|
+
* `enabled` is authoritative in the subgraph (the contract derives it as
|
|
175
|
+
* quorum > 0 and emits it on VouchConfigSet), so it is used directly rather
|
|
176
|
+
* than re-derived from flags.
|
|
177
|
+
*/
|
|
178
|
+
function vouchConfigFromSubgraph(row) {
|
|
179
|
+
if (!row)
|
|
180
|
+
return null;
|
|
181
|
+
if (row.quorum === null || row.quorum === undefined)
|
|
182
|
+
return null;
|
|
183
|
+
if (row.enabled === null || row.enabled === undefined)
|
|
184
|
+
return null;
|
|
185
|
+
if (row.membershipHatId === null || row.membershipHatId === undefined)
|
|
186
|
+
return null;
|
|
187
|
+
if (row.combinesWithHierarchy === null || row.combinesWithHierarchy === undefined)
|
|
188
|
+
return null;
|
|
189
|
+
return {
|
|
190
|
+
quorum: Number(row.quorum),
|
|
191
|
+
membershipHatId: String(row.membershipHatId),
|
|
192
|
+
enabled: Boolean(row.enabled),
|
|
193
|
+
combineWithHierarchy: Boolean(row.combinesWithHierarchy),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/** The Graph caps a page at 1000 entities; a full page means "cannot derive". */
|
|
197
|
+
const VOUCH_PAGE_CAP = 1000;
|
|
198
|
+
/**
|
|
199
|
+
* Subgraph read of a hat's vouch config. Returns null — meaning "fall back to
|
|
200
|
+
* the RPC getter" — when the query fails, when the row is missing (absence is
|
|
201
|
+
* ambiguous between never-configured and not-yet-indexed), or when a field is
|
|
202
|
+
* null on an older deployment.
|
|
203
|
+
*/
|
|
204
|
+
async function fetchVouchConfigFromSubgraph(client, eligibilityModuleAddress, hatId, chainId) {
|
|
205
|
+
try {
|
|
206
|
+
const data = await client.query(vouch_1.FETCH_VOUCH_CONFIG, { eligibilityModuleId: eligibilityModuleAddress, hatId: hatId.toString() }, chainId);
|
|
207
|
+
return vouchConfigFromSubgraph(data?.vouchConfigs?.[0]);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Port of `pop vouch config show` — src/commands/vouch/config.ts.
|
|
215
|
+
* Config is SUBGRAPH-FIRST (display only) with getVouchConfig() as fallback;
|
|
216
|
+
* `preferSubgraph: false` (the CLI's explicit --rpc) pins the config read to
|
|
217
|
+
* the provider. getMaxDailyVouches() has no subgraph field and stays on chain.
|
|
218
|
+
*/
|
|
219
|
+
async function readVouchConfigStatus(client, provider, eligibilityModuleAddress, hatId, opts = {}) {
|
|
220
|
+
const contract = (0, contracts_1.createReadContract)(eligibilityModuleAddress, 'EligibilityModuleNew', provider);
|
|
221
|
+
const [subgraphConfig, maxDailyVouches] = await Promise.all([
|
|
222
|
+
opts.preferSubgraph !== false
|
|
223
|
+
? fetchVouchConfigFromSubgraph(client, eligibilityModuleAddress, hatId, opts.chainId)
|
|
224
|
+
: Promise.resolve(null),
|
|
225
|
+
contract.getMaxDailyVouches(),
|
|
226
|
+
]);
|
|
227
|
+
const config = subgraphConfig ?? decodeVouchConfig(await contract.getVouchConfig(hatId));
|
|
228
|
+
return {
|
|
229
|
+
config,
|
|
230
|
+
maxDailyVouches: Number(maxDailyVouches),
|
|
231
|
+
source: subgraphConfig ? 'subgraph' : 'rpc',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* TIER 0 reader — reproduce `currentVouchCount(hatId, wearer)` from the subgraph's
|
|
236
|
+
* epoch mirror. Returns null to mean "cannot derive, fall back to RPC".
|
|
237
|
+
*/
|
|
238
|
+
function wearerCountFromEpochMirror(data, configRow) {
|
|
239
|
+
const configEpoch = configRow?.epoch;
|
|
240
|
+
if (configEpoch === null || configEpoch === undefined)
|
|
241
|
+
return null;
|
|
242
|
+
const states = data?.wearerVouchStates;
|
|
243
|
+
if (!Array.isArray(states))
|
|
244
|
+
return null;
|
|
245
|
+
// No row means the wearer has never been vouched for on this hat. The config row
|
|
246
|
+
// proved the module is indexed, so absence here is a real zero rather than lag.
|
|
247
|
+
if (states.length === 0)
|
|
248
|
+
return 0;
|
|
249
|
+
const state = states[0];
|
|
250
|
+
if (state?.count === null || state?.count === undefined)
|
|
251
|
+
return null;
|
|
252
|
+
if (state?.epoch === null || state?.epoch === undefined)
|
|
253
|
+
return null;
|
|
254
|
+
// The contract's getter, verbatim: a tally from a superseded epoch reads as zero.
|
|
255
|
+
// String compare because epochs are uint256 and exceed Number's safe range.
|
|
256
|
+
const count = String(state.epoch) === String(configEpoch) ? Number(state.count) : 0;
|
|
257
|
+
if (!Number.isFinite(count))
|
|
258
|
+
return null;
|
|
259
|
+
// effectiveCount is the subgraph's own materialisation of the same expression, kept
|
|
260
|
+
// in step by a sweep. Disagreement means the deployment is buggy — refuse to pick a
|
|
261
|
+
// winner and let the authoritative on-chain getter answer.
|
|
262
|
+
const materialised = state?.effectiveCount;
|
|
263
|
+
if (materialised !== null && materialised !== undefined && Number(materialised) !== count) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
return count;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* TIER 1 reader — count active Vouch rows on deployments with no epoch mirror.
|
|
270
|
+
* Returns null to mean "cannot derive, fall back to RPC".
|
|
271
|
+
*/
|
|
272
|
+
function wearerCountFromVouchRows(data, configRow) {
|
|
273
|
+
const vouches = data?.vouches;
|
|
274
|
+
if (!Array.isArray(vouches) || vouches.length >= VOUCH_PAGE_CAP)
|
|
275
|
+
return null;
|
|
276
|
+
// EPOCH GUARD. These deployments do not index the vouch epoch at all, and the
|
|
277
|
+
// contract emits no event when a configureVouching/resetVouches invalidates every
|
|
278
|
+
// outstanding vouch — so the superseded rows are still isActive here. A vouch
|
|
279
|
+
// created BEFORE the config's last update may therefore be dead on chain, and
|
|
280
|
+
// counting it would report quorum met when the contract says otherwise. Block-number
|
|
281
|
+
// ordering is the only signal available, so any such row makes the count underivable.
|
|
282
|
+
const configBlock = Number(configRow?.updatedAtBlock ?? 0);
|
|
283
|
+
if (configBlock > 0 && vouches.some((v) => Number(v?.createdAtBlock ?? 0) < configBlock)) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return vouches.length;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Subgraph read of a hat's vouch config PLUS the wearer's current vouch count.
|
|
290
|
+
* Returns null to mean "fall back to RPC".
|
|
291
|
+
*
|
|
292
|
+
* Two tiers (graph/documents/vouch.ts): the epoch mirror when the deployment indexes
|
|
293
|
+
* it — which is exact, and lets a reconfigured hat still be served from the subgraph —
|
|
294
|
+
* and the legacy active-row count, which can only refuse to answer once an epoch bump
|
|
295
|
+
* is suspected. `queryWithFieldFallback` drops to the legacy tier on the unknown-field
|
|
296
|
+
* validation error, so this works before and after the subgraph ships.
|
|
297
|
+
*/
|
|
298
|
+
async function fetchWearerVouchStateFromSubgraph(client, eligibilityModuleAddress, hatId, wearer, chainId) {
|
|
299
|
+
const variables = {
|
|
300
|
+
eligibilityModuleId: eligibilityModuleAddress,
|
|
301
|
+
hatId: hatId.toString(),
|
|
302
|
+
wearer,
|
|
303
|
+
};
|
|
304
|
+
try {
|
|
305
|
+
const { data, tierIndex } = await client.queryWithFieldFallback([
|
|
306
|
+
{ query: vouch_1.FETCH_VOUCH_STATUS_EPOCH_AWARE, variables },
|
|
307
|
+
{ query: vouch_1.FETCH_VOUCH_STATUS, variables },
|
|
308
|
+
], { chainId });
|
|
309
|
+
const configRow = data?.vouchConfigs?.[0];
|
|
310
|
+
const config = vouchConfigFromSubgraph(configRow);
|
|
311
|
+
if (!config)
|
|
312
|
+
return null;
|
|
313
|
+
const currentCount = tierIndex === 0
|
|
314
|
+
? wearerCountFromEpochMirror(data, configRow)
|
|
315
|
+
: wearerCountFromVouchRows(data, configRow);
|
|
316
|
+
if (currentCount === null)
|
|
317
|
+
return null;
|
|
318
|
+
return { config, currentCount };
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* RPC read of the same wearer-side state, batched into one round-trip.
|
|
326
|
+
* `enabled` is derived from the config flags rather than a separate
|
|
327
|
+
* isVouchingEnabled() call — the contract's getter is literally
|
|
328
|
+
* `_isVouchingEnabled(vouchConfigs[hatId].flags)` (VERIFIED in
|
|
329
|
+
* src/EligibilityModule.sol), so the answer is identical.
|
|
330
|
+
*/
|
|
331
|
+
async function readWearerVouchStateOnchain(provider, eligibilityModuleAddress, hatId, wearer) {
|
|
332
|
+
const [rawConfig, currentCount] = await batchEligibilityReads(provider, eligibilityModuleAddress, [
|
|
333
|
+
{ fn: 'vouchConfigs', args: [hatId] },
|
|
334
|
+
{ fn: 'currentVouchCount', args: [hatId, wearer] },
|
|
335
|
+
]);
|
|
336
|
+
return {
|
|
337
|
+
config: decodeVouchConfig(rawConfig),
|
|
338
|
+
currentCount: ethers_1.ethers.BigNumber.from(currentCount).toNumber(),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Wearer-side vouch progress, SUBGRAPH-FIRST with the RPC batch as fallback.
|
|
343
|
+
* Display-only (`pop vouch status`) — never used to gate a write.
|
|
344
|
+
*
|
|
345
|
+
* `preferSubgraph: false` (set when the caller pinned an explicit RPC node)
|
|
346
|
+
* skips the subgraph entirely so an explicitly targeted node stays the
|
|
347
|
+
* source of truth.
|
|
348
|
+
*/
|
|
349
|
+
async function readWearerVouchState(client, provider, eligibilityModuleAddress, hatId, wearer, opts = {}) {
|
|
350
|
+
if (opts.preferSubgraph !== false) {
|
|
351
|
+
const fromSubgraph = await fetchWearerVouchStateFromSubgraph(client, eligibilityModuleAddress, hatId, wearer, opts.chainId);
|
|
352
|
+
if (fromSubgraph)
|
|
353
|
+
return { state: fromSubgraph, source: 'subgraph' };
|
|
354
|
+
}
|
|
355
|
+
const state = await readWearerVouchStateOnchain(provider, eligibilityModuleAddress, hatId, wearer);
|
|
356
|
+
return { state, source: 'rpc' };
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Read the module's superAdmin address (public getter, verified).
|
|
360
|
+
*
|
|
361
|
+
* NOT sourced from the subgraph even though EligibilityModuleContract
|
|
362
|
+
* .superAdmin is populated and matches on chain (verified: module
|
|
363
|
+
* 0x27114cb7… → 0x23f90b38… on both). Every caller is requireSuperAdmin, a
|
|
364
|
+
* pure revert predictor for the onlySuperAdmin modifier. Indexing lag over a
|
|
365
|
+
* transferSuperAdmin would either broadcast a doomed admin transaction (the
|
|
366
|
+
* outgoing admin still looks authorised) or hard-block the incoming one, and
|
|
367
|
+
* these are all write paths where one extra eth_call is noise next to the
|
|
368
|
+
* transaction itself.
|
|
369
|
+
*/
|
|
370
|
+
async function getSuperAdmin(provider, eligibilityModuleAddress) {
|
|
371
|
+
const contract = (0, contracts_1.createReadContract)(eligibilityModuleAddress, 'EligibilityModuleNew', provider);
|
|
372
|
+
return await contract.superAdmin();
|
|
373
|
+
}
|
|
374
|
+
/** Shared superAdmin mismatch/unreadable errors for both requireSuperAdmin forms. */
|
|
375
|
+
function assertSuperAdmin(admin, signerAddress, action) {
|
|
376
|
+
if (admin.toLowerCase() !== signerAddress.toLowerCase()) {
|
|
377
|
+
throw new errors_1.PreconditionError(`${action} is superAdmin-only — the module superAdmin is ${admin}, but you are signing as ${signerAddress}.`, 'Run with the superAdmin key. If the superAdmin is the org executor, route this change through a governance proposal instead.');
|
|
378
|
+
}
|
|
379
|
+
return admin;
|
|
380
|
+
}
|
|
381
|
+
function unreadableSuperAdmin(eligibilityModuleAddress) {
|
|
382
|
+
return new errors_1.PreconditionError(`Could not read superAdmin() from the eligibility module at ${eligibilityModuleAddress}.`, 'Check RPC connectivity and that the org has an EligibilityModule deployed.');
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Fail fast (exit 4) when the signer is not the module superAdmin, naming
|
|
386
|
+
* who the superAdmin actually is — BEFORE any gas is spent. Returns the
|
|
387
|
+
* superAdmin address on success. All EligibilityModule admin writes are
|
|
388
|
+
* onlySuperAdmin (verified, see module header).
|
|
389
|
+
*/
|
|
390
|
+
async function requireSuperAdmin(provider, eligibilityModuleAddress, signerAddress, action) {
|
|
391
|
+
let admin;
|
|
392
|
+
try {
|
|
393
|
+
admin = await getSuperAdmin(provider, eligibilityModuleAddress);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
throw unreadableSuperAdmin(eligibilityModuleAddress);
|
|
397
|
+
}
|
|
398
|
+
return assertSuperAdmin(admin, signerAddress, action);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* requireSuperAdmin PLUS one extra pre-flight read, in ONE Multicall3
|
|
402
|
+
* round-trip instead of two sequential eth_calls to the same contract.
|
|
403
|
+
*
|
|
404
|
+
* The two are independent: a superAdmin() failure raises the same
|
|
405
|
+
* PreconditionError requireSuperAdmin does, while a failure of the extra read
|
|
406
|
+
* (older module without the getter, for instance) is returned as
|
|
407
|
+
* `{ extra: null, extraError }` so callers can keep downgrading it to a
|
|
408
|
+
* skipped soft check rather than blocking the write.
|
|
409
|
+
*/
|
|
410
|
+
async function requireSuperAdminWithRead(provider, eligibilityModuleAddress, signerAddress, action, extra) {
|
|
411
|
+
const [adminResult, extraResult] = await batchEligibilityReadsSettled(provider, eligibilityModuleAddress, [{ fn: 'superAdmin', args: [] }, extra]);
|
|
412
|
+
if (adminResult.status === 'rejected')
|
|
413
|
+
throw unreadableSuperAdmin(eligibilityModuleAddress);
|
|
414
|
+
const admin = assertSuperAdmin(adminResult.value, signerAddress, action);
|
|
415
|
+
return extraResult.status === 'fulfilled'
|
|
416
|
+
? { admin, extra: extraResult.value, extraError: null }
|
|
417
|
+
: { admin, extra: null, extraError: extraResult.reason };
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* The four gate reads, in batch order. ALL FOUR ARE RPC-ONLY and cannot be
|
|
421
|
+
* served by the subgraph (verified 2026-07-30 against poa-gnosis-v-1):
|
|
422
|
+
* canUserVouch — composite of join-grace + daily limit, no field
|
|
423
|
+
* getCurrentDailyVouchCount — no field (derivable from Vouch.createdAt, but
|
|
424
|
+
* only by re-deriving the contract's UTC-day
|
|
425
|
+
* bucketing client-side; left on RPC)
|
|
426
|
+
* getMaxDailyVouches — EligibilityModuleContract has no maxDailyVouches
|
|
427
|
+
* field even though MaxDailyVouchesSet is emitted
|
|
428
|
+
* getUserJoinTime — UserJoinTime entity has ZERO rows live
|
|
429
|
+
*/
|
|
430
|
+
const VOUCHER_GATE_READS = (voucher) => [
|
|
431
|
+
{ fn: 'canUserVouch', args: [voucher] },
|
|
432
|
+
{ fn: 'getCurrentDailyVouchCount', args: [voucher] },
|
|
433
|
+
{ fn: 'getMaxDailyVouches', args: [] },
|
|
434
|
+
{ fn: 'getUserJoinTime', args: [voucher] },
|
|
435
|
+
];
|
|
436
|
+
function decodeVoucherGate(raw) {
|
|
437
|
+
const [canVouch, dailyUsed, maxDaily, joinTime] = raw;
|
|
438
|
+
return {
|
|
439
|
+
canVouch: Boolean(canVouch),
|
|
440
|
+
dailyUsed: ethers_1.ethers.BigNumber.from(dailyUsed).toNumber(),
|
|
441
|
+
maxDaily: ethers_1.ethers.BigNumber.from(maxDaily).toNumber(),
|
|
442
|
+
joinTime: ethers_1.ethers.BigNumber.from(joinTime).toNumber(),
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Read the signer-side vouching gate in ONE Multicall3 round-trip:
|
|
447
|
+
* canUserVouch (the authoritative on-chain answer), getCurrentDailyVouchCount
|
|
448
|
+
* vs getMaxDailyVouches, and getUserJoinTime for the new-account grace.
|
|
449
|
+
*/
|
|
450
|
+
async function readVoucherGate(provider, eligibilityModuleAddress, voucher) {
|
|
451
|
+
const raw = await batchEligibilityReads(provider, eligibilityModuleAddress, VOUCHER_GATE_READS(voucher));
|
|
452
|
+
return decodeVoucherGate(raw);
|
|
453
|
+
}
|
|
454
|
+
/** Approximate new-user grace used only for the unlock estimate (deployed v4 modules). */
|
|
455
|
+
const NEW_USER_GRACE_SECONDS = 2 * 86400;
|
|
456
|
+
/**
|
|
457
|
+
* Human reason why a signer cannot vouch right now, derived from the gate
|
|
458
|
+
* reads. canUserVouch is authoritative; this maps its `false` to the same
|
|
459
|
+
* friendly reasons the contract enforces (daily rate limit, else the
|
|
460
|
+
* new-account grace). Returns null when the signer can vouch.
|
|
461
|
+
*/
|
|
462
|
+
function vouchRestriction(gate, now = Math.floor(Date.now() / 1000)) {
|
|
463
|
+
if (gate.canVouch)
|
|
464
|
+
return null;
|
|
465
|
+
if (gate.dailyUsed >= gate.maxDaily) {
|
|
466
|
+
const nextUtcMidnight = (Math.floor(now / 86400) + 1) * 86400;
|
|
467
|
+
return {
|
|
468
|
+
message: `Daily vouch limit reached (${gate.dailyUsed}/${gate.maxDaily} used today).`,
|
|
469
|
+
suggestion: `The counter resets at the next UTC midnight (${(0, format_1.formatRelativeTime)(nextUtcMidnight, now)}).`,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
const unlockAt = gate.joinTime > 0 ? gate.joinTime + NEW_USER_GRACE_SECONDS : 0;
|
|
473
|
+
const unlockHint = unlockAt > now
|
|
474
|
+
? `Vouching unlocks ${(0, format_1.formatRelativeTime)(unlockAt, now)} (about 2 days after joining).`
|
|
475
|
+
: 'New accounts must wait out a short grace period before vouching.';
|
|
476
|
+
return {
|
|
477
|
+
message: 'Account too new — new members cannot vouch during the join grace period.',
|
|
478
|
+
suggestion: unlockHint,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
/** "2/3 used today" quota fragment shared by vouch status + vouch for. */
|
|
482
|
+
function vouchQuotaLabel(gate) {
|
|
483
|
+
return `${gate.dailyUsed}/${gate.maxDaily} used today`;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Pre-flight reads for `pop vouch for`, in ONE Multicall3 round-trip (was six
|
|
487
|
+
* separate eth_calls): the signer gate, the hat's vouch config
|
|
488
|
+
* (enabled/quorum), and the wearer's current count for the progress line.
|
|
489
|
+
*
|
|
490
|
+
* Deliberately RPC — this gates a write, and every field predicts a revert
|
|
491
|
+
* (VouchingNotEnabled, NewUserVouchingRestricted, VouchingRateLimitExceeded).
|
|
492
|
+
*/
|
|
493
|
+
async function readVouchPreflight(provider, eligibilityModuleAddress, hatId, voucher, wearer) {
|
|
494
|
+
const raw = await batchEligibilityReads(provider, eligibilityModuleAddress, [
|
|
495
|
+
...VOUCHER_GATE_READS(voucher),
|
|
496
|
+
{ fn: 'getVouchConfig', args: [hatId] },
|
|
497
|
+
{ fn: 'currentVouchCount', args: [hatId, wearer] },
|
|
498
|
+
]);
|
|
499
|
+
const [rawConfig, currentCount] = raw.slice(4);
|
|
500
|
+
return {
|
|
501
|
+
gate: decodeVoucherGate(raw.slice(0, 4)),
|
|
502
|
+
config: decodeVouchConfig(rawConfig),
|
|
503
|
+
currentCount: ethers_1.ethers.BigNumber.from(currentCount).toNumber(),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Fail fast (exit 4) on every vouch precondition the contract would revert
|
|
508
|
+
* on: vouching disabled, daily rate limit, new-account grace. Mirrors
|
|
509
|
+
* vouchFor's own gate order so the friendly message matches the revert the
|
|
510
|
+
* tx would have produced. Port of `pop vouch for`'s gateVouch —
|
|
511
|
+
* src/commands/vouch/for.ts.
|
|
512
|
+
*/
|
|
513
|
+
function gateVouch(state, hatId) {
|
|
514
|
+
if (!state.config.enabled) {
|
|
515
|
+
throw new errors_1.PreconditionError(`Vouching is not enabled for hat ${hatId}.`, 'Inspect/enable it with: pop vouch config show --hat ' + hatId);
|
|
516
|
+
}
|
|
517
|
+
const restriction = vouchRestriction(state.gate);
|
|
518
|
+
if (restriction) {
|
|
519
|
+
throw new errors_1.PreconditionError(restriction.message, restriction.suggestion);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Read getWearerStatus + vouch progress for a claim pre-flight, in ONE
|
|
524
|
+
* Multicall3 round-trip (was three eth_calls).
|
|
525
|
+
*
|
|
526
|
+
* getWearerStatus stays RPC on purpose. Besides gating a write, the subgraph's
|
|
527
|
+
* WearerEligibility entity is NOT equivalent: it only materialises when an
|
|
528
|
+
* admin sets a wearer-specific rule. VERIFIED live — wearer
|
|
529
|
+
* 0x1302e867… reached quorum on hat 29089782865237956866… and
|
|
530
|
+
* getWearerStatus() returns (true, true) on chain, yet the Gnosis subgraph has
|
|
531
|
+
* ZERO WearerEligibility rows for that address. Sourcing the claim gate from
|
|
532
|
+
* the subgraph would reject every vouch-eligible claimer.
|
|
533
|
+
*/
|
|
534
|
+
async function readClaimGate(provider, eligibilityModuleAddress, wearer, hatId) {
|
|
535
|
+
const [status, rawConfig, currentCount] = await batchEligibilityReads(provider, eligibilityModuleAddress, [
|
|
536
|
+
{ fn: 'getWearerStatus', args: [wearer, hatId] },
|
|
537
|
+
{ fn: 'getVouchConfig', args: [hatId] },
|
|
538
|
+
{ fn: 'currentVouchCount', args: [hatId, wearer] },
|
|
539
|
+
]);
|
|
540
|
+
return {
|
|
541
|
+
eligible: Boolean(status.eligible ?? status[0]),
|
|
542
|
+
standing: Boolean(status.standing ?? status[1]),
|
|
543
|
+
config: decodeVouchConfig(rawConfig),
|
|
544
|
+
currentCount: ethers_1.ethers.BigNumber.from(currentCount).toNumber(),
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* True when the signer's own vouch record exists (raw, epoch-unaware).
|
|
549
|
+
*
|
|
550
|
+
* RPC-only: the sole caller is `pop vouch revoke`'s pre-flight, which exists
|
|
551
|
+
* to predict revokeVouch's HasNotVouched revert. The subgraph could answer it
|
|
552
|
+
* (Vouch filtered on hatId+wearer+voucher+isActive is populated), but a stale
|
|
553
|
+
* "false" blocks a legitimate revoke and a stale "true" broadcasts a doomed
|
|
554
|
+
* one.
|
|
555
|
+
*/
|
|
556
|
+
async function hasVouched(provider, eligibilityModuleAddress, hatId, wearer, voucher) {
|
|
557
|
+
const contract = (0, contracts_1.createReadContract)(eligibilityModuleAddress, 'EligibilityModuleNew', provider);
|
|
558
|
+
return Boolean(await contract.hasVouched(hatId, wearer, voucher));
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Revoke pre-flight, RPC-only (it predicts a revert, so indexing lag is not
|
|
562
|
+
* acceptable) and batched into ONE round-trip.
|
|
563
|
+
*
|
|
564
|
+
* `hasVouched` ALONE IS NOT SUFFICIENT: the getter returns the raw `vouchers`
|
|
565
|
+
* mapping with no epoch filter (VERIFIED, contracts origin/main — it is a bare
|
|
566
|
+
* `return _layout().vouchers[hatId][wearer][voucher]`), while revokeVouch first
|
|
567
|
+
* requires `wearerVouchEpoch[hatId][wearer] == vouchConfigEpoch[hatId]`. After a
|
|
568
|
+
* configureVouching/resetVouches/clearWearerVouches, hasVouched still says true
|
|
569
|
+
* and the transaction still reverts HasNotVouched.
|
|
570
|
+
*
|
|
571
|
+
* currentVouchCount closes most of that gap: it returns 0 for exactly the stale-epoch
|
|
572
|
+
* case, and a zero count also makes the `newCount = count - 1` underflow unavoidable —
|
|
573
|
+
* so `currentCount == 0` guarantees a revert and can be blocked with no false negatives.
|
|
574
|
+
*
|
|
575
|
+
* One residual case stays unpredictable off-chain: the wearer's epoch is current
|
|
576
|
+
* (someone else vouched after the bump) but THIS voucher's record is not. There is no
|
|
577
|
+
* getter for voucherRecordEpoch, so the decoded HasNotVouched revert still covers it.
|
|
578
|
+
*/
|
|
579
|
+
async function readRevokeGate(provider, eligibilityModuleAddress, hatId, wearer, voucher) {
|
|
580
|
+
const [vouched, currentCount] = await batchEligibilityReads(provider, eligibilityModuleAddress, [
|
|
581
|
+
{ fn: 'hasVouched', args: [hatId, wearer, voucher] },
|
|
582
|
+
{ fn: 'currentVouchCount', args: [hatId, wearer] },
|
|
583
|
+
]);
|
|
584
|
+
return {
|
|
585
|
+
hasVouched: Boolean(vouched),
|
|
586
|
+
currentCount: ethers_1.ethers.BigNumber.from(currentCount).toNumber(),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* True when `applicant` has an active application for `hatId`.
|
|
591
|
+
*
|
|
592
|
+
* RPC-only. RoleApplication.active IS populated on the live subgraph, but the
|
|
593
|
+
* ONLY caller of this helper is `pop role withdraw-application`'s pre-flight
|
|
594
|
+
* (src/commands/role/withdraw-application.ts), which predicts the
|
|
595
|
+
* NoActiveApplication revert — there is no display caller. `pop role
|
|
596
|
+
* applications` reads the subgraph via listRoleApplications below.
|
|
597
|
+
*/
|
|
598
|
+
async function hasActiveApplication(provider, eligibilityModuleAddress, hatId, applicant) {
|
|
599
|
+
const contract = (0, contracts_1.createReadContract)(eligibilityModuleAddress, 'EligibilityModuleNew', provider);
|
|
600
|
+
return Boolean(await contract.hasActiveApplication(hatId, applicant));
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Read paused() (superAdmin emergency-pause state).
|
|
604
|
+
*
|
|
605
|
+
* RPC-only. EligibilityModuleContract.isPaused is populated and agrees with
|
|
606
|
+
* chain (verified: false on module 0x27114cb7… both ways), but the only caller
|
|
607
|
+
* is `pop role admin pause/unpause`, which uses it to predict OpenZeppelin's
|
|
608
|
+
* ExpectedPause / EnforcedPause revert.
|
|
609
|
+
*/
|
|
610
|
+
async function isPaused(provider, eligibilityModuleAddress) {
|
|
611
|
+
const contract = (0, contracts_1.createReadContract)(eligibilityModuleAddress, 'EligibilityModuleNew', provider);
|
|
612
|
+
return Boolean(await contract.paused());
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Port of `pop vouch list` — src/commands/vouch/list.ts. Pure subgraph
|
|
616
|
+
* (FETCH_VOUCHES_FOR_ORG); display only, never a quorum decision (see the
|
|
617
|
+
* document's isActive epoch note in graph/documents/vouch.ts). `hatId` filter
|
|
618
|
+
* is applied client-side by string equality, exactly as the CLI does.
|
|
619
|
+
*/
|
|
620
|
+
async function listVouches(client, eligibilityModuleAddress, opts = {}) {
|
|
621
|
+
const result = await client.query(vouch_1.FETCH_VOUCHES_FOR_ORG, { eligibilityModuleId: eligibilityModuleAddress }, opts.chainId);
|
|
622
|
+
let vouches = result.vouches || [];
|
|
623
|
+
if (opts.hatId) {
|
|
624
|
+
vouches = vouches.filter((v) => v.hatId === opts.hatId);
|
|
625
|
+
}
|
|
626
|
+
return vouches;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Port of `pop role applications` — src/commands/role/applications.ts.
|
|
630
|
+
* Pure subgraph: FETCH_USER_ROLE_APPLICATIONS when `applicant` is given
|
|
631
|
+
* (lowercased, as the CLI does), else FETCH_ALL_ROLE_APPLICATIONS — both
|
|
632
|
+
* filtered by eligibilityModuleId. `hatId` filter is applied client-side by
|
|
633
|
+
* string equality, exactly as the CLI does.
|
|
634
|
+
*/
|
|
635
|
+
async function listRoleApplications(client, eligibilityModuleAddress, opts = {}) {
|
|
636
|
+
const result = opts.applicant
|
|
637
|
+
? await client.query(role_1.FETCH_USER_ROLE_APPLICATIONS, { eligibilityModuleId: eligibilityModuleAddress, applicant: opts.applicant.toLowerCase() }, opts.chainId)
|
|
638
|
+
: await client.query(role_1.FETCH_ALL_ROLE_APPLICATIONS, { eligibilityModuleId: eligibilityModuleAddress }, opts.chainId);
|
|
639
|
+
let applications = result.roleApplications || [];
|
|
640
|
+
if (opts.hatId) {
|
|
641
|
+
applications = applications.filter((a) => a.hatId === opts.hatId);
|
|
642
|
+
}
|
|
643
|
+
return applications;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Resolve a role name to its hat ID by querying the org's roles.
|
|
647
|
+
* Port of `pop vouch for`'s resolveRoleToHatId — src/commands/vouch/for.ts —
|
|
648
|
+
* minus the TTY disambiguation select (CLI UX): exact match first, then
|
|
649
|
+
* partial; multiple matches always fail with the candidate list.
|
|
650
|
+
*/
|
|
651
|
+
async function resolveRoleNameToHatId(client, orgIdOrName, roleName, chainId) {
|
|
652
|
+
const orgId = await (0, resolve_1.resolveOrgId)(client, orgIdOrName, chainId);
|
|
653
|
+
const query = `{
|
|
654
|
+
organization(id: "${orgId}") {
|
|
655
|
+
roles(where: { isUserRole: true }) { hatId name }
|
|
656
|
+
}
|
|
657
|
+
}`;
|
|
658
|
+
const results = await client.queryAllChains(query, {});
|
|
659
|
+
const matches = [];
|
|
660
|
+
for (const r of results) {
|
|
661
|
+
const roles = r.data?.organization?.roles || [];
|
|
662
|
+
for (const role of roles) {
|
|
663
|
+
if (role.name && role.name.toLowerCase() === roleName.toLowerCase()) {
|
|
664
|
+
matches.push({ hatId: role.hatId, name: role.name });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (matches.length === 0) {
|
|
669
|
+
// Try partial match
|
|
670
|
+
for (const r of results) {
|
|
671
|
+
const roles = r.data?.organization?.roles || [];
|
|
672
|
+
for (const role of roles) {
|
|
673
|
+
if (role.name && role.name.toLowerCase().includes(roleName.toLowerCase())) {
|
|
674
|
+
matches.push({ hatId: role.hatId, name: role.name });
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (matches.length === 0) {
|
|
680
|
+
throw new errors_1.CliError(`No role named "${roleName}" found.`, exit_codes_1.EXIT.USAGE, 'Use pop org roles to list available roles.');
|
|
681
|
+
}
|
|
682
|
+
if (matches.length > 1) {
|
|
683
|
+
const names = matches.map(m => `${m.name} (${m.hatId})`).join(', ');
|
|
684
|
+
throw new errors_1.CliError(`Multiple roles match "${roleName}": ${names}.`, exit_codes_1.EXIT.USAGE, 'Use --hat with the specific hat ID.');
|
|
685
|
+
}
|
|
686
|
+
return matches[0].hatId;
|
|
687
|
+
}
|