@sage-protocol/sdk 0.1.23 → 0.1.25
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 +1 -1
- package/dist/browser/index.mjs +463 -37
- package/dist/index.cjs +2277 -804
- package/dist/index.mjs +2277 -804
- package/dist/node/index.cjs +2277 -804
- package/dist/node/index.mjs +2277 -804
- package/package.json +1 -1
- package/types/index.d.ts +49 -1
package/dist/index.mjs
CHANGED
|
@@ -20,7 +20,7 @@ var require_package = __commonJS({
|
|
|
20
20
|
"package.json"(exports2, module2) {
|
|
21
21
|
module2.exports = {
|
|
22
22
|
name: "@sage-protocol/sdk",
|
|
23
|
-
version: "0.1.
|
|
23
|
+
version: "0.1.25",
|
|
24
24
|
description: "Backend-agnostic SDK for interacting with the Sage Protocol (governance, SubDAOs, tokens).",
|
|
25
25
|
main: "dist/index.cjs",
|
|
26
26
|
module: "dist/index.mjs",
|
|
@@ -112,20 +112,21 @@ var require_abi = __commonJS({
|
|
|
112
112
|
var SubDAO = [
|
|
113
113
|
"function governor() view returns (address)",
|
|
114
114
|
"function timelock() view returns (address)",
|
|
115
|
+
// SubDAO ops Safe / treasury (multisig) address. On newer deployments opsSafe() is an alias.
|
|
116
|
+
"function treasury() view returns (address)",
|
|
117
|
+
"function opsSafe() view returns (address)",
|
|
115
118
|
"function getGovernanceMode() view returns (uint8)",
|
|
119
|
+
// New governance profile (3-axis model). May be unavailable on legacy SubDAOs.
|
|
120
|
+
"function profileInitialized() view returns (bool)",
|
|
121
|
+
"function getGovernanceProfile() view returns (uint8 kind, uint8 proposalAccess, uint8 executionAccess)",
|
|
116
122
|
"function promptRegistry() view returns (address)",
|
|
117
123
|
"function sageTreasury() view returns (address)",
|
|
118
|
-
"function stakeToken() view returns (address)",
|
|
119
|
-
"function minStakeAmount() view returns (uint256)",
|
|
120
124
|
"function accessModel() view returns (uint8)",
|
|
121
125
|
"function membershipPolicy() view returns (uint8)",
|
|
122
126
|
"function profileCID() view returns (string)",
|
|
123
127
|
"function factoryDeployer() view returns (address)",
|
|
124
|
-
"function stakedAmount(address) view returns (uint256)",
|
|
125
128
|
"function userForkCount(address) view returns (uint256)",
|
|
126
129
|
"function forkCount(string) view returns (uint256)",
|
|
127
|
-
"function stake(uint256)",
|
|
128
|
-
"function unstake(uint256)",
|
|
129
130
|
// Fork functions
|
|
130
131
|
"function forkSubDAO(string,string)",
|
|
131
132
|
"function forkSubDAO(string,string,bool)",
|
|
@@ -165,7 +166,7 @@ var require_abi = __commonJS({
|
|
|
165
166
|
var FactoryWrite = [
|
|
166
167
|
"function createSubDAO(string name, string description, uint8 accessModel, uint256 minStakeAmount, uint256 burnAmount) returns (address subDAO, address registry)",
|
|
167
168
|
"function createSubDAOWithStable(string name, string description, uint8 accessModel, uint256 minStakeAmount, (uint256 value,uint256 deadline,uint8 v,bytes32 r,bytes32 s) permit) returns (address subDAO, address registry)",
|
|
168
|
-
"function createSubDAOWithParams(string name, string description, uint8 accessModel, uint256 minStakeAmount, uint256 burnAmount, uint256 votingDelay, uint256 votingPeriod, uint256 proposalThreshold, uint256 quorumPercentage, uint8 initialForkPolicy, uint8 initialMembershipPolicy, string profileCID) returns (address subDAO, address registry)",
|
|
169
|
+
"function createSubDAOWithParams(string name, string description, uint8 accessModel, uint256 minStakeAmount, uint256 burnAmount, uint256 votingDelay, uint256 votingPeriod, uint256 proposalThreshold, uint256 quorumPercentage, uint8 initialForkPolicy, uint8 initialMembershipPolicy, string profileCID, string manifestCID, string manifestVersion) returns (address subDAO, address registry)",
|
|
169
170
|
"function createSubDAOOperatorWithStable(string name, string description, uint8 accessModel, uint256 minStakeAmount, address operatorExecutor, address operatorAdmin, (uint256 value,uint256 deadline,uint8 v,bytes32 r,bytes32 s) permit) returns (address subDAO, address registry)",
|
|
170
171
|
"function createSubDAOOperatorWithStableAdvanced(string name, string description, uint8 accessModel, uint256 minStakeAmount, address operatorExecutor, address operatorAdmin, bool anyoneExec, bool governorProposer, (uint256 value,uint256 deadline,uint8 v,bytes32 r,bytes32 s) permit) returns (address subDAO, address registry)",
|
|
171
172
|
"function createSubDAOOperator(string name, string description, uint8 accessModel, uint256 minStakeAmount, uint256 burnAmount, address operatorExecutor, address operatorAdmin) returns (address subDAO, address registry)",
|
|
@@ -182,19 +183,21 @@ var require_abi = __commonJS({
|
|
|
182
183
|
"function maxCreationBurn() view returns (uint256)"
|
|
183
184
|
];
|
|
184
185
|
var LibraryRegistry = [
|
|
185
|
-
"function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version, address forkedFromDAO, uint256 sxxxForkFee)",
|
|
186
|
+
"function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version, address forkedFromDAO, uint256 sxxxForkFee, uint256 nonce)",
|
|
186
187
|
"function daoTimelock(address) view returns (address)",
|
|
187
|
-
"function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version, address forkedFromDAO, uint256 sxxxForkFee))",
|
|
188
|
+
"function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version, address forkedFromDAO, uint256 sxxxForkFee, uint256 nonce))",
|
|
188
189
|
"function getLibraryForkFee(address dao) view returns (uint256)",
|
|
189
190
|
"function setLibraryForkFee(address dao, uint256 fee)",
|
|
190
191
|
"function updateLibrary(address dao, string manifestCID, string version)",
|
|
192
|
+
"function updateLibraryCAS(address dao, string manifestCID, string version, uint256 expectedNonce)",
|
|
191
193
|
"function registerDAO(address dao, address timelock)",
|
|
192
194
|
"function registerForkedDAO(address childDAO, address childTimelock, address parentDAO, string manifestCID, string version)",
|
|
193
195
|
"function initializeLibraryFromFork(address sourceDao, address forkedDao)",
|
|
194
|
-
"event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version)",
|
|
196
|
+
"event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version, uint256 nonce)",
|
|
195
197
|
"event DAORegistered(address indexed dao, address indexed timelock)",
|
|
196
198
|
"event LibraryForked(address indexed sourceDao, address indexed forkedDao, string manifestCID)",
|
|
197
|
-
"event LibraryForkFeeUpdated(address indexed dao, uint256 fee)"
|
|
199
|
+
"event LibraryForkFeeUpdated(address indexed dao, uint256 fee)",
|
|
200
|
+
"error NonceMismatch(uint256 expected, uint256 actual)"
|
|
198
201
|
];
|
|
199
202
|
var PromptRegistry = [
|
|
200
203
|
"function prompts(string key) view returns (string cid, uint256 version, uint256 timestamp, address author, string forkedFromCID, address originalAuthor, bool isFork, uint256 forkDepth)",
|
|
@@ -221,14 +224,23 @@ var require_abi = __commonJS({
|
|
|
221
224
|
];
|
|
222
225
|
var Governor = [
|
|
223
226
|
"function name() view returns (string)",
|
|
227
|
+
// ERC6372 clock (Governor uses this for vote snapshots)
|
|
228
|
+
"function clock() view returns (uint48)",
|
|
224
229
|
"function timelock() view returns (address)",
|
|
225
230
|
"function _executor() view returns (address)",
|
|
226
231
|
"function sxxxToken() view returns (address)",
|
|
227
232
|
"function votingDelay() view returns (uint256)",
|
|
228
233
|
"function votingPeriod() view returns (uint256)",
|
|
229
234
|
"function proposalThreshold() view returns (uint256)",
|
|
235
|
+
"function minVotesToVote() view returns (uint256)",
|
|
236
|
+
// OZ Governor: voting power at a past timepoint
|
|
237
|
+
"function getVotes(address,uint256) view returns (uint256)",
|
|
230
238
|
// OZ Governor: quorum at a past block
|
|
231
239
|
"function quorum(uint256) view returns (uint256)",
|
|
240
|
+
// Fixed quorumVotes model (PromptGovernor v2+)
|
|
241
|
+
"function quorumVotes() view returns (uint256)",
|
|
242
|
+
"function setQuorumVotes(uint256)",
|
|
243
|
+
// Legacy OZ Governor: percentage-based quorum (deprecated)
|
|
232
244
|
"function quorumNumerator() view returns (uint256)",
|
|
233
245
|
"function proposalStakeAmount(uint256) view returns (uint256)",
|
|
234
246
|
"function state(uint256) view returns (uint8)",
|
|
@@ -241,9 +253,24 @@ var require_abi = __commonJS({
|
|
|
241
253
|
"function castVoteWithReason(uint256 proposalId, uint8 support, string reason) returns (uint256)",
|
|
242
254
|
"function queue(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash)",
|
|
243
255
|
"function execute(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash)",
|
|
256
|
+
"function cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) returns (uint256)",
|
|
244
257
|
// Some Governors expose id-based queue/execute via TimelockController
|
|
245
258
|
"function queue(uint256)",
|
|
246
|
-
"function execute(uint256)"
|
|
259
|
+
"function execute(uint256)",
|
|
260
|
+
// Threshold model (PromptGovernor)
|
|
261
|
+
"function proposalProposer(uint256 proposalId) view returns (address)",
|
|
262
|
+
"function proposalThresholdSnapshot(uint256 proposalId) view returns (uint256)",
|
|
263
|
+
"function proposalCreator(uint256 proposalId) view returns (address)",
|
|
264
|
+
"function proposalCooldown() view returns (uint256)",
|
|
265
|
+
"function lastProposalTimestamp(address) view returns (uint256)",
|
|
266
|
+
"function getProposalCooldownInfo(address) view returns (bool canPropose, uint256 cooldownRemaining)",
|
|
267
|
+
"function canCreateProposal(address) view returns (bool)",
|
|
268
|
+
"function setMinVotesToVote(uint256)",
|
|
269
|
+
// Multiplier helpers
|
|
270
|
+
"function baseVotesToken() view returns (address)",
|
|
271
|
+
"function isMultiplierEnabled() view returns (bool)",
|
|
272
|
+
"function multiplierNFT() view returns (address)",
|
|
273
|
+
"function getVotingBreakdown(address) view returns (uint256 baseVotes, uint256 multiplier, uint256 effectiveVotes)"
|
|
247
274
|
];
|
|
248
275
|
var Timelock = [
|
|
249
276
|
"function getMinDelay() view returns (uint256)",
|
|
@@ -463,13 +490,11 @@ var require_abi = __commonJS({
|
|
|
463
490
|
"function governanceConfig() view returns (address)",
|
|
464
491
|
"function communityTreasury() view returns (address)",
|
|
465
492
|
"function governanceContract() view returns (address)",
|
|
466
|
-
"function libraryRegistry() view returns (address)",
|
|
467
493
|
"function subdao() view returns (address)",
|
|
468
494
|
"function soulboundBadge() view returns (address)",
|
|
469
495
|
// Admin
|
|
470
496
|
"function setGovernanceContract(address _gov)",
|
|
471
497
|
"function setCommunityTreasury(address _treasury)",
|
|
472
|
-
"function setLibraryRegistry(address _registry)",
|
|
473
498
|
"function setSoulboundBadge(address _sbtAddress)",
|
|
474
499
|
"function grantRole(bytes32 role, address account)",
|
|
475
500
|
"function revokeRole(bytes32 role, address account)",
|
|
@@ -483,7 +508,7 @@ var require_abi = __commonJS({
|
|
|
483
508
|
"event VotingStarted(uint256 indexed bountyId, uint256 votingEndTime)",
|
|
484
509
|
"event BountyCompleted(uint256 indexed bountyId, address indexed winner, uint256 reward, string deliverable)",
|
|
485
510
|
"event BountyAutoApproved(uint256 indexed bountyId, address indexed winner)",
|
|
486
|
-
"event
|
|
511
|
+
"event BountyWinnerSelected(uint256 indexed bountyId, address indexed subdao, string libraryKey, string winnerCid, address indexed winner, uint8 libraryAction)",
|
|
487
512
|
"event BountyCancelled(uint256 indexed bountyId, address indexed creator)",
|
|
488
513
|
"event BountyExpired(uint256 indexed bountyId)",
|
|
489
514
|
"event BountyRewardIncreased(uint256 indexed bountyId, uint256 additionalReward, uint256 newTotal)",
|
|
@@ -1577,193 +1602,496 @@ var require_transports = __commonJS({
|
|
|
1577
1602
|
}
|
|
1578
1603
|
});
|
|
1579
1604
|
|
|
1580
|
-
// src/
|
|
1581
|
-
var
|
|
1582
|
-
"src/
|
|
1583
|
-
function
|
|
1584
|
-
if (!
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1594
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1595
|
-
return `0x${hashHex}`;
|
|
1596
|
-
}
|
|
1597
|
-
function keccak256Sync(data) {
|
|
1598
|
-
const str = typeof data === "string" ? data : JSON.stringify(data);
|
|
1599
|
-
let hash = 0;
|
|
1600
|
-
for (let i = 0; i < str.length; i++) {
|
|
1601
|
-
const char = str.charCodeAt(i);
|
|
1602
|
-
hash = (hash << 5) - hash + char;
|
|
1603
|
-
hash = hash & hash;
|
|
1605
|
+
// src/utils/logs.js
|
|
1606
|
+
var require_logs = __commonJS({
|
|
1607
|
+
"src/utils/logs.js"(exports2, module2) {
|
|
1608
|
+
async function fetchLogsChunked({ provider, address, topics = [], fromBlock = 0, toBlock = "latest", chunkSize = 5e4 }) {
|
|
1609
|
+
if (!provider) throw new Error("provider required");
|
|
1610
|
+
const latest = toBlock === "latest" ? await provider.getBlockNumber() : Number(toBlock);
|
|
1611
|
+
let start = Number(fromBlock);
|
|
1612
|
+
const logs = [];
|
|
1613
|
+
while (start <= latest) {
|
|
1614
|
+
const end = Math.min(start + chunkSize - 1, latest);
|
|
1615
|
+
const chunk = await provider.getLogs({ address, topics, fromBlock: start, toBlock: end });
|
|
1616
|
+
logs.push(...chunk);
|
|
1617
|
+
start = end + 1;
|
|
1604
1618
|
}
|
|
1605
|
-
|
|
1606
|
-
return `0x${hex.repeat(8)}`.slice(0, 66);
|
|
1619
|
+
return logs;
|
|
1607
1620
|
}
|
|
1608
1621
|
module2.exports = {
|
|
1609
|
-
|
|
1610
|
-
keccak256Async,
|
|
1611
|
-
keccak256Sync
|
|
1622
|
+
fetchLogsChunked
|
|
1612
1623
|
};
|
|
1613
1624
|
}
|
|
1614
1625
|
});
|
|
1615
1626
|
|
|
1616
|
-
// src/
|
|
1617
|
-
var
|
|
1618
|
-
"src/
|
|
1619
|
-
var { getAddress } =
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
const v = (direction || "").toString().toLowerCase();
|
|
1626
|
-
return v === "asc" ? "asc" : "desc";
|
|
1627
|
-
}
|
|
1628
|
-
function safeGetAddress(value) {
|
|
1627
|
+
// src/subdao/index.js
|
|
1628
|
+
var require_subdao = __commonJS({
|
|
1629
|
+
"src/subdao/index.js"(exports2, module2) {
|
|
1630
|
+
var { Contract, Interface, getAddress } = __require("ethers");
|
|
1631
|
+
var ABI = require_abi();
|
|
1632
|
+
var { fetchLogsChunked } = require_logs();
|
|
1633
|
+
var { SageSDKError, CODES } = require_errors();
|
|
1634
|
+
function normalise(address, label) {
|
|
1635
|
+
if (!address) throw new SageSDKError(CODES.INVALID_ARGS, `${label} required`);
|
|
1629
1636
|
try {
|
|
1630
|
-
return getAddress(
|
|
1631
|
-
} catch {
|
|
1632
|
-
|
|
1637
|
+
return getAddress(address);
|
|
1638
|
+
} catch (err) {
|
|
1639
|
+
throw new SageSDKError(CODES.INVALID_ARGS, `invalid ${label}`, { cause: err });
|
|
1633
1640
|
}
|
|
1634
1641
|
}
|
|
1635
|
-
function
|
|
1642
|
+
async function discoverSubDAOs({ provider, factoryAddress, fromBlock = 0, toBlock = "latest", limit = 5e3, chunkSize = 5e4 }) {
|
|
1643
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
1644
|
+
const fac = normalise(factoryAddress, "factoryAddress");
|
|
1645
|
+
const iface = new Interface(ABI.Factory);
|
|
1646
|
+
const topic = iface.getEvent("SubDAOGovernanceDeployed").topicHash;
|
|
1647
|
+
const logs = await fetchLogsChunked({ provider, address: fac, topics: [topic], fromBlock, toBlock, chunkSize });
|
|
1636
1648
|
const out = [];
|
|
1637
|
-
|
|
1649
|
+
const slice = limit > 0 ? logs.slice(-limit) : logs;
|
|
1650
|
+
for (const log of slice) {
|
|
1638
1651
|
try {
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1641
|
-
|
|
1652
|
+
const decoded = iface.decodeEventLog("SubDAOGovernanceDeployed", log.data, log.topics);
|
|
1653
|
+
out.push({
|
|
1654
|
+
subdao: getAddress(decoded[0]),
|
|
1655
|
+
governor: getAddress(decoded[1]),
|
|
1656
|
+
timelock: getAddress(decoded[2]),
|
|
1657
|
+
treasury: decoded[3] ? getAddress(decoded[3]) : null,
|
|
1658
|
+
txHash: log.transactionHash,
|
|
1659
|
+
blockNumber: log.blockNumber
|
|
1660
|
+
});
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
continue;
|
|
1642
1663
|
}
|
|
1643
1664
|
}
|
|
1644
1665
|
return out;
|
|
1645
1666
|
}
|
|
1646
|
-
async function
|
|
1647
|
-
if (!
|
|
1648
|
-
const
|
|
1649
|
-
const
|
|
1667
|
+
async function getSubDAOInfo({ provider, subdao }) {
|
|
1668
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
1669
|
+
const addr = normalise(subdao, "subdao");
|
|
1670
|
+
const contract = new Contract(addr, ABI.SubDAO, provider);
|
|
1671
|
+
const [governor, timelock, treasury, mode, promptRegistry, sageTreasury, accessModel, membershipPolicy, profileCID, factoryDeployer] = await Promise.all([
|
|
1672
|
+
contract.governor(),
|
|
1673
|
+
contract.timelock(),
|
|
1674
|
+
// The ops Safe address (multisig) controlling treasury/admin ops.
|
|
1675
|
+
contract.treasury().catch(() => null),
|
|
1676
|
+
contract.getGovernanceMode().catch(() => null),
|
|
1677
|
+
contract.promptRegistry().catch(() => null),
|
|
1678
|
+
contract.sageTreasury().catch(() => null),
|
|
1679
|
+
contract.accessModel().catch(() => null),
|
|
1680
|
+
contract.membershipPolicy().catch(() => null),
|
|
1681
|
+
contract.profileCID().catch(() => null),
|
|
1682
|
+
contract.factoryDeployer().catch(() => null)
|
|
1683
|
+
]);
|
|
1684
|
+
return {
|
|
1685
|
+
address: addr,
|
|
1686
|
+
governor: getAddress(governor),
|
|
1687
|
+
timelock: getAddress(timelock),
|
|
1688
|
+
treasury: treasury && treasury !== "0x0000000000000000000000000000000000000000" ? getAddress(treasury) : null,
|
|
1689
|
+
governanceMode: mode,
|
|
1690
|
+
promptRegistry: promptRegistry ? getAddress(promptRegistry) : null,
|
|
1691
|
+
sageTreasury: sageTreasury ? getAddress(sageTreasury) : null,
|
|
1692
|
+
accessModel: accessModel != null ? Number(accessModel) : null,
|
|
1693
|
+
membershipPolicy: membershipPolicy != null ? Number(membershipPolicy) : null,
|
|
1694
|
+
profileCID: profileCID || null,
|
|
1695
|
+
factoryDeployer: factoryDeployer && factoryDeployer !== "0x0000000000000000000000000000000000000000" ? getAddress(factoryDeployer) : null
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
function decodeGovernanceProfile({ kind, proposalAccess, executionAccess }) {
|
|
1699
|
+
const kindLabel = Number(kind) === 0 ? "OPERATOR" : Number(kind) === 1 ? "TOKEN" : null;
|
|
1700
|
+
const proposalLabel = Number(proposalAccess) === 0 ? "COUNCIL_ONLY" : Number(proposalAccess) === 1 ? "COMMUNITY_THRESHOLD" : null;
|
|
1701
|
+
const execLabel = Number(executionAccess) === 0 ? "COUNCIL_ONLY" : Number(executionAccess) === 1 ? "ANYONE" : null;
|
|
1702
|
+
return {
|
|
1703
|
+
kind: kindLabel,
|
|
1704
|
+
proposalAccess: proposalLabel,
|
|
1705
|
+
executionAccess: execLabel
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
async function getGovernanceProfile({ provider, subdao }) {
|
|
1709
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
1710
|
+
const addr = normalise(subdao, "subdao");
|
|
1711
|
+
const contract = new Contract(addr, ABI.SubDAO, provider);
|
|
1712
|
+
let profileInitialized = false;
|
|
1713
|
+
let source = "legacy";
|
|
1650
1714
|
try {
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1715
|
+
profileInitialized = !!await contract.profileInitialized();
|
|
1716
|
+
source = profileInitialized ? "profile" : "legacy";
|
|
1717
|
+
} catch (_) {
|
|
1718
|
+
profileInitialized = false;
|
|
1719
|
+
source = "legacy";
|
|
1720
|
+
}
|
|
1721
|
+
if (profileInitialized) {
|
|
1722
|
+
try {
|
|
1723
|
+
const [kind, proposalAccess, executionAccess] = await contract.getGovernanceProfile();
|
|
1724
|
+
return {
|
|
1725
|
+
...decodeGovernanceProfile({ kind, proposalAccess, executionAccess }),
|
|
1726
|
+
profileInitialized: true,
|
|
1727
|
+
source
|
|
1728
|
+
};
|
|
1729
|
+
} catch (_) {
|
|
1730
|
+
profileInitialized = false;
|
|
1731
|
+
source = "legacy";
|
|
1664
1732
|
}
|
|
1665
|
-
return data.data;
|
|
1666
|
-
} catch (error) {
|
|
1667
|
-
clearTimeout(timeoutId);
|
|
1668
|
-
throw error;
|
|
1669
1733
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
proposals(where: { governor: $governor }, first: $first, skip: $skip, orderBy: createdAt, orderDirection: desc) {
|
|
1675
|
-
id
|
|
1676
|
-
proposer
|
|
1677
|
-
description
|
|
1678
|
-
createdAt
|
|
1679
|
-
targets
|
|
1680
|
-
values
|
|
1681
|
-
calldatas
|
|
1734
|
+
let mode = null;
|
|
1735
|
+
try {
|
|
1736
|
+
mode = await contract.getGovernanceMode();
|
|
1737
|
+
} catch (_) {
|
|
1682
1738
|
}
|
|
1683
|
-
|
|
1684
|
-
`, { governor: String(governor).toLowerCase(), first, skip });
|
|
1685
|
-
return mapSafe(data?.proposals, (p) => {
|
|
1686
|
-
const proposer = safeGetAddress(p.proposer);
|
|
1687
|
-
if (!proposer) return null;
|
|
1688
|
-
const targets = (p.targets || []).map((t) => safeGetAddress(t)).filter(Boolean);
|
|
1689
|
-
if (!targets.length && (p.targets || []).length) return null;
|
|
1739
|
+
if (mode != null && Number(mode) === 0) {
|
|
1690
1740
|
return {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
values: (p.values || []).map((value) => BigInt(String(value))),
|
|
1697
|
-
calldatas: p.calldatas || []
|
|
1741
|
+
kind: "OPERATOR",
|
|
1742
|
+
proposalAccess: "COUNCIL_ONLY",
|
|
1743
|
+
executionAccess: "COUNCIL_ONLY",
|
|
1744
|
+
profileInitialized: false,
|
|
1745
|
+
source
|
|
1698
1746
|
};
|
|
1699
|
-
});
|
|
1700
|
-
}
|
|
1701
|
-
var STATE_STRING_TO_NUMBER = {
|
|
1702
|
-
PENDING: 0,
|
|
1703
|
-
ACTIVE: 1,
|
|
1704
|
-
CANCELED: 2,
|
|
1705
|
-
CANCELLED: 2,
|
|
1706
|
-
// some indexes may use British spelling
|
|
1707
|
-
DEFEATED: 3,
|
|
1708
|
-
SUCCEEDED: 4,
|
|
1709
|
-
QUEUED: 5,
|
|
1710
|
-
EXPIRED: 6,
|
|
1711
|
-
EXECUTED: 7
|
|
1712
|
-
};
|
|
1713
|
-
async function listProposalsFiltered({ url, governor, states, fromTimestamp, toTimestamp, first = 20, skip = 0, orderBy = "createdAt", orderDirection = "desc" }) {
|
|
1714
|
-
const govLower = governor ? String(governor).toLowerCase() : null;
|
|
1715
|
-
const statesUpper = Array.isArray(states) && states.length ? states.map((s) => String(s).toUpperCase()) : null;
|
|
1716
|
-
const safeOrderBy = sanitizeOrderBy(orderBy, ["createdAt", "updatedAt", "eta"], "createdAt");
|
|
1717
|
-
const safeOrderDirection = sanitizeOrderDirection(orderDirection, "desc");
|
|
1718
|
-
const doc = `
|
|
1719
|
-
query($first: Int!, $skip: Int!, $governor: Bytes, $states: [String!], $from: Int, $to: Int) {
|
|
1720
|
-
proposals(
|
|
1721
|
-
where: {
|
|
1722
|
-
${governor ? "governor: $governor," : ""}
|
|
1723
|
-
${statesUpper ? "state_in: $states," : ""}
|
|
1724
|
-
${fromTimestamp !== void 0 ? "createdAt_gte: $from," : ""}
|
|
1725
|
-
${toTimestamp !== void 0 ? "createdAt_lte: $to," : ""}
|
|
1726
|
-
}
|
|
1727
|
-
first: $first
|
|
1728
|
-
skip: $skip
|
|
1729
|
-
orderBy: ${safeOrderBy}
|
|
1730
|
-
orderDirection: ${safeOrderDirection}
|
|
1731
|
-
) {
|
|
1732
|
-
id
|
|
1733
|
-
proposer
|
|
1734
|
-
description
|
|
1735
|
-
createdAt
|
|
1736
|
-
updatedAt
|
|
1737
|
-
state
|
|
1738
|
-
eta
|
|
1739
|
-
targets
|
|
1740
|
-
values
|
|
1741
|
-
calldatas
|
|
1742
1747
|
}
|
|
1743
|
-
|
|
1744
|
-
`;
|
|
1745
|
-
const variables = { first, skip };
|
|
1746
|
-
if (govLower) variables.governor = govLower;
|
|
1747
|
-
if (statesUpper) variables.states = statesUpper;
|
|
1748
|
-
if (fromTimestamp !== void 0) variables.from = Number(fromTimestamp);
|
|
1749
|
-
if (toTimestamp !== void 0) variables.to = Number(toTimestamp);
|
|
1750
|
-
const data = await query(url, doc, variables);
|
|
1751
|
-
return mapSafe(data?.proposals, (p) => {
|
|
1752
|
-
const stateStr = String(p.state || "").toUpperCase();
|
|
1753
|
-
const stateNum = STATE_STRING_TO_NUMBER[stateStr] != null ? STATE_STRING_TO_NUMBER[stateStr] : null;
|
|
1754
|
-
const proposer = safeGetAddress(p.proposer);
|
|
1755
|
-
if (!proposer) return null;
|
|
1756
|
-
const targets = (p.targets || []).map((t) => safeGetAddress(t)).filter(Boolean);
|
|
1757
|
-
if (!targets.length && (p.targets || []).length) return null;
|
|
1748
|
+
if (mode != null && Number(mode) === 1) {
|
|
1758
1749
|
return {
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1750
|
+
kind: "TOKEN",
|
|
1751
|
+
proposalAccess: "COMMUNITY_THRESHOLD",
|
|
1752
|
+
executionAccess: "ANYONE",
|
|
1753
|
+
profileInitialized: false,
|
|
1754
|
+
source
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
return {
|
|
1758
|
+
kind: null,
|
|
1759
|
+
proposalAccess: null,
|
|
1760
|
+
executionAccess: null,
|
|
1761
|
+
profileInitialized: false,
|
|
1762
|
+
source
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
async function getSubDAOUserStats({ provider, subdao, account }) {
|
|
1766
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
1767
|
+
const addr = normalise(subdao, "subdao");
|
|
1768
|
+
const user = normalise(account, "account");
|
|
1769
|
+
const contract = new Contract(addr, ABI.SubDAO, provider);
|
|
1770
|
+
const userForkCount = await contract.userForkCount(user).catch(() => 0n);
|
|
1771
|
+
return {
|
|
1772
|
+
userForkCount: Number(userForkCount)
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
var SubDAOInterface = new Interface([
|
|
1776
|
+
// Optional on newer SubDAO implementations; provided for tx building only.
|
|
1777
|
+
"function setProfileCid(string)"
|
|
1778
|
+
]);
|
|
1779
|
+
function buildSetProfileCidTx({ subdao, profileCid }) {
|
|
1780
|
+
const addr = normalise(subdao, "subdao");
|
|
1781
|
+
if (!profileCid || typeof profileCid !== "string" || !profileCid.trim()) {
|
|
1782
|
+
throw new SageSDKError(CODES.INVALID_ARGS, "profileCid (CID string) required");
|
|
1783
|
+
}
|
|
1784
|
+
const data = SubDAOInterface.encodeFunctionData("setProfileCid", [String(profileCid)]);
|
|
1785
|
+
return { to: addr, data, value: 0n };
|
|
1786
|
+
}
|
|
1787
|
+
module2.exports = {
|
|
1788
|
+
discoverSubDAOs,
|
|
1789
|
+
getSubDAOInfo,
|
|
1790
|
+
getGovernanceProfile,
|
|
1791
|
+
getSubDAOUserStats,
|
|
1792
|
+
buildSetProfileCidTx
|
|
1793
|
+
};
|
|
1794
|
+
async function ensureSxxxBurnAllowance({ signer, sxxx, owner, spender, amount }) {
|
|
1795
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
1796
|
+
if (!sxxx) throw new SageSDKError(CODES.INVALID_ARGS, "sxxx address required");
|
|
1797
|
+
if (!spender) throw new SageSDKError(CODES.INVALID_ARGS, "spender (factory) required");
|
|
1798
|
+
const sxxxAddr = getAddress(sxxx);
|
|
1799
|
+
const spenderAddr = getAddress(spender);
|
|
1800
|
+
const erc = new Contract(sxxxAddr, ABI.SXXX, signer);
|
|
1801
|
+
const ownerAddr = owner ? getAddress(owner) : await signer.getAddress();
|
|
1802
|
+
const need = BigInt(amount || 0n);
|
|
1803
|
+
if (need === 0n) return { approved: false, tx: null };
|
|
1804
|
+
const current = await erc.allowance(ownerAddr, spenderAddr);
|
|
1805
|
+
const cur = BigInt(current.toString());
|
|
1806
|
+
if (cur >= need) return { approved: false, tx: null };
|
|
1807
|
+
const tx = await erc.approve(spenderAddr, need);
|
|
1808
|
+
const rcpt = await tx.wait();
|
|
1809
|
+
return { approved: true, tx, receipt: rcpt };
|
|
1810
|
+
}
|
|
1811
|
+
async function createSubDAO({ signer, factoryAddress, name, description, accessModel, burnAmount, sxxx }) {
|
|
1812
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
1813
|
+
if (!factoryAddress) throw new SageSDKError(CODES.INVALID_ARGS, "factoryAddress required");
|
|
1814
|
+
const provider = signer.provider;
|
|
1815
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer.provider required");
|
|
1816
|
+
if (sxxx && BigInt(burnAmount || 0n) > 0n) {
|
|
1817
|
+
await ensureSxxxBurnAllowance({ signer, sxxx, spender: factoryAddress, amount: BigInt(burnAmount) });
|
|
1818
|
+
}
|
|
1819
|
+
const fac = new Contract(getAddress(factoryAddress), ABI.FactoryWrite, signer);
|
|
1820
|
+
const args = [
|
|
1821
|
+
String(name || ""),
|
|
1822
|
+
String(description || ""),
|
|
1823
|
+
Number(accessModel ?? 0),
|
|
1824
|
+
0n,
|
|
1825
|
+
// minStakeAmount deprecated
|
|
1826
|
+
BigInt(burnAmount ?? 0n)
|
|
1827
|
+
];
|
|
1828
|
+
let tx;
|
|
1829
|
+
try {
|
|
1830
|
+
tx = await fac.createSubDAO(...args);
|
|
1831
|
+
} catch (err) {
|
|
1832
|
+
tx = await fac.createSubDAO(...args, { gasLimit: 8e6 });
|
|
1833
|
+
}
|
|
1834
|
+
const receipt = await tx.wait();
|
|
1835
|
+
const iface = new Interface(ABI.Factory);
|
|
1836
|
+
let subdao = null, governor = null, timelock = null, treasury = null, registry = null;
|
|
1837
|
+
for (const log of receipt.logs || []) {
|
|
1838
|
+
try {
|
|
1839
|
+
const parsed = iface.parseLog(log);
|
|
1840
|
+
if (parsed && parsed.name === "SubDAOGovernanceDeployed") {
|
|
1841
|
+
subdao = getAddress(parsed.args.subDAO || parsed.args[0]);
|
|
1842
|
+
governor = getAddress(parsed.args.governor || parsed.args[1]);
|
|
1843
|
+
timelock = getAddress(parsed.args.timelock || parsed.args[2]);
|
|
1844
|
+
treasury = getAddress(parsed.args.treasury || parsed.args[3]);
|
|
1845
|
+
} else if (parsed && parsed.name === "SubDAOCreated") {
|
|
1846
|
+
const maybe = parsed.args.registry || parsed.args[2];
|
|
1847
|
+
if (maybe) registry = getAddress(maybe);
|
|
1848
|
+
if (!subdao) subdao = getAddress(parsed.args.subDAO || parsed.args[0]);
|
|
1849
|
+
}
|
|
1850
|
+
} catch (_) {
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
try {
|
|
1854
|
+
if (subdao && !registry) {
|
|
1855
|
+
const sub = new Contract(subdao, ABI.SubDAO, provider);
|
|
1856
|
+
const reg = await sub.promptRegistry().catch(() => null);
|
|
1857
|
+
if (reg && /^0x/.test(String(reg))) registry = getAddress(reg);
|
|
1858
|
+
}
|
|
1859
|
+
} catch (_) {
|
|
1860
|
+
}
|
|
1861
|
+
return { tx, receipt, subdao, governor, timelock, treasury, registry };
|
|
1862
|
+
}
|
|
1863
|
+
async function makeOperator({ signer, subdao, operator, grantAdmin = false }) {
|
|
1864
|
+
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
1865
|
+
const provider = signer.provider;
|
|
1866
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer.provider required");
|
|
1867
|
+
const addr = normalise(subdao, "subdao");
|
|
1868
|
+
const sub = new Contract(addr, ABI.SubDAO, provider);
|
|
1869
|
+
const governor = getAddress(await sub.governor());
|
|
1870
|
+
const timelock = getAddress(await sub.timelock());
|
|
1871
|
+
const TL_ABI = [
|
|
1872
|
+
"function PROPOSER_ROLE() view returns (bytes32)",
|
|
1873
|
+
"function EXECUTOR_ROLE() view returns (bytes32)",
|
|
1874
|
+
"function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
|
|
1875
|
+
"function grantRole(bytes32,address)",
|
|
1876
|
+
"function revokeRole(bytes32,address)"
|
|
1877
|
+
];
|
|
1878
|
+
const tl = new Contract(timelock, TL_ABI, signer);
|
|
1879
|
+
const [PR, ER, AR] = await Promise.all([
|
|
1880
|
+
tl.PROPOSER_ROLE(),
|
|
1881
|
+
tl.EXECUTOR_ROLE(),
|
|
1882
|
+
tl.DEFAULT_ADMIN_ROLE().catch(() => null)
|
|
1883
|
+
]);
|
|
1884
|
+
const ops = [];
|
|
1885
|
+
ops.push(await tl.grantRole(PR, getAddress(operator)));
|
|
1886
|
+
ops.push(await tl.grantRole(ER, getAddress(operator)));
|
|
1887
|
+
ops.push(await tl.revokeRole(PR, governor));
|
|
1888
|
+
if (grantAdmin && AR) ops.push(await tl.grantRole(AR, getAddress(operator)));
|
|
1889
|
+
const receipts = [];
|
|
1890
|
+
for (const tx of ops) {
|
|
1891
|
+
receipts.push(await tx.wait());
|
|
1892
|
+
}
|
|
1893
|
+
return { governor, timelock, operator: getAddress(operator), txs: ops, receipts };
|
|
1894
|
+
}
|
|
1895
|
+
async function createOperatorSubDAO({ signer, factoryAddress, operator, name, description, accessModel, burnAmount, sxxx, grantAdmin = false }) {
|
|
1896
|
+
const created = await createSubDAO({ signer, factoryAddress, name, description, accessModel, burnAmount, sxxx });
|
|
1897
|
+
if (!created.subdao) throw new SageSDKError(CODES.UNKNOWN, "createSubDAO did not emit SubDAO address");
|
|
1898
|
+
const op = await makeOperator({ signer, subdao: created.subdao, operator, grantAdmin });
|
|
1899
|
+
return { ...created, operatorResult: op };
|
|
1900
|
+
}
|
|
1901
|
+
module2.exports.ensureSxxxBurnAllowance = ensureSxxxBurnAllowance;
|
|
1902
|
+
module2.exports.createSubDAO = createSubDAO;
|
|
1903
|
+
module2.exports.makeOperator = makeOperator;
|
|
1904
|
+
module2.exports.createOperatorSubDAO = createOperatorSubDAO;
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
|
|
1908
|
+
// src/browser/utils.js
|
|
1909
|
+
var require_utils = __commonJS({
|
|
1910
|
+
"src/browser/utils.js"(exports2, module2) {
|
|
1911
|
+
function getAddress(address) {
|
|
1912
|
+
if (!address) return address;
|
|
1913
|
+
const addr = String(address).trim();
|
|
1914
|
+
const normalized = addr.startsWith("0x") ? addr.slice(2).toLowerCase() : addr.toLowerCase();
|
|
1915
|
+
return `0x${normalized}`;
|
|
1916
|
+
}
|
|
1917
|
+
async function keccak256Async(data) {
|
|
1918
|
+
const encoder = new TextEncoder();
|
|
1919
|
+
const dataBuffer = typeof data === "string" ? encoder.encode(data) : data;
|
|
1920
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", dataBuffer);
|
|
1921
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
1922
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1923
|
+
return `0x${hashHex}`;
|
|
1924
|
+
}
|
|
1925
|
+
function keccak256Sync(data) {
|
|
1926
|
+
const str = typeof data === "string" ? data : JSON.stringify(data);
|
|
1927
|
+
let hash = 0;
|
|
1928
|
+
for (let i = 0; i < str.length; i++) {
|
|
1929
|
+
const char = str.charCodeAt(i);
|
|
1930
|
+
hash = (hash << 5) - hash + char;
|
|
1931
|
+
hash = hash & hash;
|
|
1932
|
+
}
|
|
1933
|
+
const hex = Math.abs(hash).toString(16).padStart(8, "0");
|
|
1934
|
+
return `0x${hex.repeat(8)}`.slice(0, 66);
|
|
1935
|
+
}
|
|
1936
|
+
module2.exports = {
|
|
1937
|
+
getAddress,
|
|
1938
|
+
keccak256Async,
|
|
1939
|
+
keccak256Sync
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
|
|
1944
|
+
// src/browser/subgraph.js
|
|
1945
|
+
var require_subgraph2 = __commonJS({
|
|
1946
|
+
"src/browser/subgraph.js"(exports2, module2) {
|
|
1947
|
+
var { getAddress } = require_utils();
|
|
1948
|
+
function sanitizeOrderBy(orderBy, allowed, fallback) {
|
|
1949
|
+
const value = (orderBy || "").toString();
|
|
1950
|
+
return allowed.includes(value) ? value : fallback;
|
|
1951
|
+
}
|
|
1952
|
+
function sanitizeOrderDirection(direction, fallback = "desc") {
|
|
1953
|
+
const v = (direction || "").toString().toLowerCase();
|
|
1954
|
+
return v === "asc" ? "asc" : "desc";
|
|
1955
|
+
}
|
|
1956
|
+
function safeGetAddress(value) {
|
|
1957
|
+
try {
|
|
1958
|
+
return getAddress(value);
|
|
1959
|
+
} catch {
|
|
1960
|
+
return null;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
function mapSafe(list, mapper) {
|
|
1964
|
+
const out = [];
|
|
1965
|
+
for (const item of list || []) {
|
|
1966
|
+
try {
|
|
1967
|
+
const v = mapper(item);
|
|
1968
|
+
if (v != null) out.push(v);
|
|
1969
|
+
} catch {
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
return out;
|
|
1973
|
+
}
|
|
1974
|
+
async function query(url, document, variables) {
|
|
1975
|
+
if (!url) throw new Error("subgraph url required");
|
|
1976
|
+
const controller = new AbortController();
|
|
1977
|
+
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
1978
|
+
try {
|
|
1979
|
+
const resp = await fetch(url, {
|
|
1980
|
+
method: "POST",
|
|
1981
|
+
headers: { "Content-Type": "application/json" },
|
|
1982
|
+
body: JSON.stringify({ query: document, variables }),
|
|
1983
|
+
signal: controller.signal
|
|
1984
|
+
});
|
|
1985
|
+
clearTimeout(timeoutId);
|
|
1986
|
+
if (!resp.ok) {
|
|
1987
|
+
throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
|
|
1988
|
+
}
|
|
1989
|
+
const data = await resp.json();
|
|
1990
|
+
if (data && data.errors) {
|
|
1991
|
+
throw new Error(data.errors.map((e) => e.message).join("; "));
|
|
1992
|
+
}
|
|
1993
|
+
return data.data;
|
|
1994
|
+
} catch (error) {
|
|
1995
|
+
clearTimeout(timeoutId);
|
|
1996
|
+
throw error;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
async function listProposals({ url, governor, first = 20, skip = 0 }) {
|
|
2000
|
+
const data = await query(url, `
|
|
2001
|
+
query($governor: Bytes!, $first: Int!, $skip: Int!) {
|
|
2002
|
+
proposals(where: { governor: $governor }, first: $first, skip: $skip, orderBy: createdAt, orderDirection: desc) {
|
|
2003
|
+
id
|
|
2004
|
+
proposer
|
|
2005
|
+
description
|
|
2006
|
+
createdAt
|
|
2007
|
+
targets
|
|
2008
|
+
values
|
|
2009
|
+
calldatas
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
`, { governor: String(governor).toLowerCase(), first, skip });
|
|
2013
|
+
return mapSafe(data?.proposals, (p) => {
|
|
2014
|
+
const proposer = safeGetAddress(p.proposer);
|
|
2015
|
+
if (!proposer) return null;
|
|
2016
|
+
const targets = (p.targets || []).map((t) => safeGetAddress(t)).filter(Boolean);
|
|
2017
|
+
if (!targets.length && (p.targets || []).length) return null;
|
|
2018
|
+
return {
|
|
2019
|
+
id: BigInt(p.id),
|
|
2020
|
+
proposer,
|
|
2021
|
+
description: p.description,
|
|
2022
|
+
createdAt: Number(p.createdAt || 0),
|
|
2023
|
+
targets,
|
|
2024
|
+
values: (p.values || []).map((value) => BigInt(String(value))),
|
|
2025
|
+
calldatas: p.calldatas || []
|
|
2026
|
+
};
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
var STATE_STRING_TO_NUMBER = {
|
|
2030
|
+
PENDING: 0,
|
|
2031
|
+
ACTIVE: 1,
|
|
2032
|
+
CANCELED: 2,
|
|
2033
|
+
CANCELLED: 2,
|
|
2034
|
+
// some indexes may use British spelling
|
|
2035
|
+
DEFEATED: 3,
|
|
2036
|
+
SUCCEEDED: 4,
|
|
2037
|
+
QUEUED: 5,
|
|
2038
|
+
EXPIRED: 6,
|
|
2039
|
+
EXECUTED: 7
|
|
2040
|
+
};
|
|
2041
|
+
async function listProposalsFiltered({ url, governor, states, fromTimestamp, toTimestamp, first = 20, skip = 0, orderBy = "createdAt", orderDirection = "desc" }) {
|
|
2042
|
+
const govLower = governor ? String(governor).toLowerCase() : null;
|
|
2043
|
+
const statesUpper = Array.isArray(states) && states.length ? states.map((s) => String(s).toUpperCase()) : null;
|
|
2044
|
+
const safeOrderBy = sanitizeOrderBy(orderBy, ["createdAt", "updatedAt", "eta"], "createdAt");
|
|
2045
|
+
const safeOrderDirection = sanitizeOrderDirection(orderDirection, "desc");
|
|
2046
|
+
const doc = `
|
|
2047
|
+
query($first: Int!, $skip: Int!, $governor: Bytes, $states: [String!], $from: Int, $to: Int) {
|
|
2048
|
+
proposals(
|
|
2049
|
+
where: {
|
|
2050
|
+
${governor ? "governor: $governor," : ""}
|
|
2051
|
+
${statesUpper ? "state_in: $states," : ""}
|
|
2052
|
+
${fromTimestamp !== void 0 ? "createdAt_gte: $from," : ""}
|
|
2053
|
+
${toTimestamp !== void 0 ? "createdAt_lte: $to," : ""}
|
|
2054
|
+
}
|
|
2055
|
+
first: $first
|
|
2056
|
+
skip: $skip
|
|
2057
|
+
orderBy: ${safeOrderBy}
|
|
2058
|
+
orderDirection: ${safeOrderDirection}
|
|
2059
|
+
) {
|
|
2060
|
+
id
|
|
2061
|
+
proposer
|
|
2062
|
+
description
|
|
2063
|
+
createdAt
|
|
2064
|
+
updatedAt
|
|
2065
|
+
state
|
|
2066
|
+
eta
|
|
2067
|
+
targets
|
|
2068
|
+
values
|
|
2069
|
+
calldatas
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
`;
|
|
2073
|
+
const variables = { first, skip };
|
|
2074
|
+
if (govLower) variables.governor = govLower;
|
|
2075
|
+
if (statesUpper) variables.states = statesUpper;
|
|
2076
|
+
if (fromTimestamp !== void 0) variables.from = Number(fromTimestamp);
|
|
2077
|
+
if (toTimestamp !== void 0) variables.to = Number(toTimestamp);
|
|
2078
|
+
const data = await query(url, doc, variables);
|
|
2079
|
+
return mapSafe(data?.proposals, (p) => {
|
|
2080
|
+
const stateStr = String(p.state || "").toUpperCase();
|
|
2081
|
+
const stateNum = STATE_STRING_TO_NUMBER[stateStr] != null ? STATE_STRING_TO_NUMBER[stateStr] : null;
|
|
2082
|
+
const proposer = safeGetAddress(p.proposer);
|
|
2083
|
+
if (!proposer) return null;
|
|
2084
|
+
const targets = (p.targets || []).map((t) => safeGetAddress(t)).filter(Boolean);
|
|
2085
|
+
if (!targets.length && (p.targets || []).length) return null;
|
|
2086
|
+
return {
|
|
2087
|
+
id: BigInt(p.id),
|
|
2088
|
+
proposer,
|
|
2089
|
+
description: p.description || "",
|
|
2090
|
+
createdAt: Number(p.createdAt || 0),
|
|
2091
|
+
updatedAt: Number(p.updatedAt || 0),
|
|
2092
|
+
state: stateStr,
|
|
2093
|
+
stateNum,
|
|
2094
|
+
eta: p.eta ? BigInt(String(p.eta)) : null,
|
|
1767
2095
|
targets,
|
|
1768
2096
|
values: (p.values || []).map((value) => BigInt(String(value))),
|
|
1769
2097
|
calldatas: p.calldatas || []
|
|
@@ -2198,6 +2526,7 @@ var require_ipfs = __commonJS({
|
|
|
2198
2526
|
var FormData = __require("form-data");
|
|
2199
2527
|
var { ethers: ethers2 } = __require("ethers");
|
|
2200
2528
|
var DEFAULT_GATEWAY = "https://ipfs.dev.sageprotocol.io/ipfs";
|
|
2529
|
+
var DEFAULT_WORKER_BASE = "https://api.sageprotocol.io";
|
|
2201
2530
|
function toLowerSafe(value, fallback = "") {
|
|
2202
2531
|
if (value == null) return fallback;
|
|
2203
2532
|
try {
|
|
@@ -2269,7 +2598,8 @@ var require_ipfs = __commonJS({
|
|
|
2269
2598
|
const formDataFactory = options.formDataFactory || (() => new FormData());
|
|
2270
2599
|
const generateCid = options.generateCid || generateDeterministicCid;
|
|
2271
2600
|
const config = {
|
|
2272
|
-
|
|
2601
|
+
// Default to 'worker' to ensure uploads go through worker for indexing
|
|
2602
|
+
provider: toLowerSafe(options.provider ?? env.SAGE_IPFS_PROVIDER ?? "worker"),
|
|
2273
2603
|
gateway: options.gateway || env.SAGE_IPFS_GATEWAY || DEFAULT_GATEWAY,
|
|
2274
2604
|
timeoutMs: Number(options.timeoutMs ?? env.SAGE_IPFS_TIMEOUT_MS ?? 15e3),
|
|
2275
2605
|
retries: Math.max(0, Number(options.retries ?? env.SAGE_IPFS_RETRIES ?? 2)),
|
|
@@ -2282,11 +2612,12 @@ var require_ipfs = __commonJS({
|
|
|
2282
2612
|
jwt: options.pinataJwt || env.PINATA_JWT || null
|
|
2283
2613
|
},
|
|
2284
2614
|
worker: {
|
|
2285
|
-
baseUrl: removeTrailingSlash(options.workerBaseUrl || env.SAGE_IPFS_WORKER_URL ||
|
|
2615
|
+
baseUrl: removeTrailingSlash(options.workerBaseUrl || env.SAGE_IPFS_WORKER_URL || DEFAULT_WORKER_BASE),
|
|
2286
2616
|
uploadUrl: removeTrailingSlash(options.workerUploadUrl || env.SAGE_IPFS_UPLOAD_URL || ""),
|
|
2287
2617
|
token: options.workerUploadToken || env.SAGE_IPFS_UPLOAD_TOKEN || "",
|
|
2288
|
-
challengePath: options.workerChallengePath || env.SAGE_IPFS_WORKER_CHALLENGE_PATH || "/auth/challenge",
|
|
2618
|
+
challengePath: options.workerChallengePath || env.SAGE_IPFS_WORKER_CHALLENGE_PATH || "/ipfs/auth/challenge",
|
|
2289
2619
|
uploadPath: options.workerUploadPath || env.SAGE_IPFS_WORKER_UPLOAD_PATH || "/ipfs/upload",
|
|
2620
|
+
uploadRawPath: options.workerUploadRawPath || env.SAGE_IPFS_WORKER_UPLOAD_RAW_PATH || "/ipfs/upload-raw",
|
|
2290
2621
|
pinPath: options.workerPinPath || env.SAGE_IPFS_WORKER_PIN_PATH || "/ipfs/pin",
|
|
2291
2622
|
warmPath: options.workerWarmPath || env.SAGE_IPFS_WORKER_WARM_PATH || "/ipfs/warm",
|
|
2292
2623
|
pinUrl: removeTrailingSlash(options.workerPinUrl || env.SAGE_IPFS_WORKER_PIN_URL || ""),
|
|
@@ -2294,11 +2625,15 @@ var require_ipfs = __commonJS({
|
|
|
2294
2625
|
signer: options.workerSigner || options.signer || null,
|
|
2295
2626
|
getAuth: options.workerGetAuth || null,
|
|
2296
2627
|
address: options.workerAddress || env.SAGE_SANDBOX_ADDRESS || "",
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2628
|
+
// All event types route to /trends/usage - worker tracks usage for trend scoring
|
|
2629
|
+
discoveryEventsPath: options.workerDiscoveryEventsPath || env.SAGE_IPFS_WORKER_DISCOVERY_EVENTS_PATH || "/trends/usage",
|
|
2630
|
+
discoveryMcpPath: options.workerDiscoveryMcpPath || env.SAGE_IPFS_WORKER_DISCOVERY_MCP_PATH || "/trends/usage",
|
|
2631
|
+
discoveryLaunchPath: options.workerDiscoveryLaunchPath || env.SAGE_IPFS_WORKER_DISCOVERY_LAUNCH_PATH || "/trends/usage",
|
|
2632
|
+
// Install events track skill installations separately for trend scoring
|
|
2633
|
+
discoveryInstallPath: options.workerDiscoveryInstallPath || env.SAGE_IPFS_WORKER_DISCOVERY_INSTALL_PATH || "/trends/install",
|
|
2300
2634
|
discoveryFeedPath: options.workerDiscoveryFeedPath || env.SAGE_IPFS_WORKER_DISCOVERY_FEED_PATH || "/discover",
|
|
2301
|
-
|
|
2635
|
+
// Metrics uses /discover/stats which returns index statistics
|
|
2636
|
+
discoveryMetricsPath: options.workerDiscoveryMetricsPath || env.SAGE_IPFS_WORKER_DISCOVERY_METRICS_PATH || "/discover/stats",
|
|
2302
2637
|
governanceReportPath: options.workerGovernanceReportPath || env.SAGE_IPFS_WORKER_GOVERNANCE_REPORT_PATH || "/governance/report",
|
|
2303
2638
|
governanceReportsPath: options.workerGovernanceReportsPath || env.SAGE_IPFS_WORKER_GOVERNANCE_REPORTS_PATH || "/governance/reports",
|
|
2304
2639
|
governanceReviewPath: options.workerGovernanceReviewPath || env.SAGE_IPFS_WORKER_GOVERNANCE_REVIEW_PATH || "/governance/report/review",
|
|
@@ -2332,8 +2667,9 @@ var require_ipfs = __commonJS({
|
|
|
2332
2667
|
const base = workerBaseUrl();
|
|
2333
2668
|
const ensure = (path2) => path2.startsWith("/") ? path2 : `/${path2}`;
|
|
2334
2669
|
if (base) {
|
|
2335
|
-
if (kind === "challenge") return `${base}${ensure(config.worker.challengePath || "/auth/challenge")}`;
|
|
2670
|
+
if (kind === "challenge") return `${base}${ensure(config.worker.challengePath || "/ipfs/auth/challenge")}`;
|
|
2336
2671
|
if (kind === "upload") return `${base}${ensure(config.worker.uploadPath || "/ipfs/upload")}`;
|
|
2672
|
+
if (kind === "upload-raw") return `${base}${ensure(config.worker.uploadRawPath || "/ipfs/upload-raw")}`;
|
|
2337
2673
|
if (kind === "pin") return `${base}${ensure(config.worker.pinPath || "/ipfs/pin")}`;
|
|
2338
2674
|
if (kind === "warm") return `${base}${ensure(config.worker.warmPath || "/ipfs/warm")}`;
|
|
2339
2675
|
if (kind === "status") return `${base}${ensure(config.worker.pinPath || "/ipfs/pin")}/${cid}`;
|
|
@@ -2368,6 +2704,26 @@ var require_ipfs = __commonJS({
|
|
|
2368
2704
|
const response = await axiosInstance.get(url, { headers, params, timeout: config.timeoutMs });
|
|
2369
2705
|
return response?.data;
|
|
2370
2706
|
}
|
|
2707
|
+
async function withRetry(fn, { retries = 2, baseMs = 500 } = {}) {
|
|
2708
|
+
let attempt = 0;
|
|
2709
|
+
let lastErr;
|
|
2710
|
+
while (attempt <= retries) {
|
|
2711
|
+
try {
|
|
2712
|
+
return await fn();
|
|
2713
|
+
} catch (e) {
|
|
2714
|
+
lastErr = e;
|
|
2715
|
+
const status = e?.response?.status;
|
|
2716
|
+
if (status && status >= 400 && status < 500 && status !== 429) {
|
|
2717
|
+
throw e;
|
|
2718
|
+
}
|
|
2719
|
+
if (attempt === retries) break;
|
|
2720
|
+
const delay = baseMs * Math.pow(2, attempt);
|
|
2721
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2722
|
+
attempt++;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
throw lastErr;
|
|
2726
|
+
}
|
|
2371
2727
|
async function buildWorkerAuthHeaders(extraHeaders = {}) {
|
|
2372
2728
|
if (config.worker.token) {
|
|
2373
2729
|
return { headers: { ...extraHeaders, Authorization: `Bearer ${config.worker.token}` }, auth: null };
|
|
@@ -2410,9 +2766,7 @@ var require_ipfs = __commonJS({
|
|
|
2410
2766
|
const hasEndpoint = workerBaseUrl() || config.worker.uploadUrl;
|
|
2411
2767
|
if (!hasEndpoint) return false;
|
|
2412
2768
|
if (!workerHasAuth()) return false;
|
|
2413
|
-
|
|
2414
|
-
if (config.provider === "auto") return true;
|
|
2415
|
-
return false;
|
|
2769
|
+
return true;
|
|
2416
2770
|
}
|
|
2417
2771
|
function resolveProviderOrder(preference) {
|
|
2418
2772
|
const order = [];
|
|
@@ -2428,8 +2782,8 @@ var require_ipfs = __commonJS({
|
|
|
2428
2782
|
console.log("[SDK DEBUG] shouldUseWorker():", shouldUseWorker());
|
|
2429
2783
|
console.log("[SDK DEBUG] hasPinataCreds():", hasPinataCreds());
|
|
2430
2784
|
}
|
|
2431
|
-
normalizedPreference.forEach(append);
|
|
2432
2785
|
if (shouldUseWorker()) append("worker");
|
|
2786
|
+
normalizedPreference.forEach(append);
|
|
2433
2787
|
if (hasPinataCreds()) append("pinata");
|
|
2434
2788
|
if (config.web3.token) append("web3");
|
|
2435
2789
|
if (config.simulate) append("simulate");
|
|
@@ -2520,6 +2874,107 @@ var require_ipfs = __commonJS({
|
|
|
2520
2874
|
}
|
|
2521
2875
|
return result;
|
|
2522
2876
|
}
|
|
2877
|
+
async function uploadRawViaWorker(content, { name, warm, gateways } = {}) {
|
|
2878
|
+
const uploadUrl = workerUrl("upload-raw");
|
|
2879
|
+
if (process.env.SAGE_DEBUG_WORKER === "1") {
|
|
2880
|
+
console.log("[SDK DEBUG] uploadRawViaWorker called");
|
|
2881
|
+
console.log("[SDK DEBUG] uploadUrl:", uploadUrl);
|
|
2882
|
+
}
|
|
2883
|
+
const { headers, auth } = await buildWorkerAuthHeaders({ "Content-Type": "application/json" });
|
|
2884
|
+
const body = { content };
|
|
2885
|
+
if (auth) body.auth = auth;
|
|
2886
|
+
if (name) body.name = name;
|
|
2887
|
+
if (process.env.SAGE_DEBUG_WORKER === "1") {
|
|
2888
|
+
console.log("[SDK DEBUG] headers:", Object.keys(headers));
|
|
2889
|
+
console.log("[SDK DEBUG] hasAuth:", !!auth);
|
|
2890
|
+
}
|
|
2891
|
+
let response;
|
|
2892
|
+
try {
|
|
2893
|
+
response = await axiosInstance.post(uploadUrl, body, { headers, timeout: config.timeoutMs });
|
|
2894
|
+
if (process.env.SAGE_DEBUG_WORKER === "1") {
|
|
2895
|
+
console.log("[SDK DEBUG] Upload response status:", response?.status);
|
|
2896
|
+
console.log("[SDK DEBUG] Upload response data:", JSON.stringify(response?.data || {}));
|
|
2897
|
+
}
|
|
2898
|
+
} catch (uploadErr) {
|
|
2899
|
+
if (process.env.SAGE_DEBUG_WORKER === "1") {
|
|
2900
|
+
console.log("[SDK DEBUG] Upload error:", uploadErr.message);
|
|
2901
|
+
console.log("[SDK DEBUG] Response status:", uploadErr.response?.status);
|
|
2902
|
+
console.log("[SDK DEBUG] Response data:", JSON.stringify(uploadErr.response?.data || {}));
|
|
2903
|
+
}
|
|
2904
|
+
const status = uploadErr.response?.status;
|
|
2905
|
+
const data2 = uploadErr.response?.data;
|
|
2906
|
+
if (status === 400 && data2?.error === "content_blocked") {
|
|
2907
|
+
const error = new Error("content_blocked");
|
|
2908
|
+
error.code = "CONTENT_BLOCKED";
|
|
2909
|
+
error.categories = data2.categories || [];
|
|
2910
|
+
error.reason = data2.reason;
|
|
2911
|
+
error.response = data2;
|
|
2912
|
+
throw error;
|
|
2913
|
+
}
|
|
2914
|
+
if (status === 402) {
|
|
2915
|
+
const error = new Error("payment_required");
|
|
2916
|
+
error.code = "PAYMENT_REQUIRED";
|
|
2917
|
+
error.creditsNeeded = data2?.creditsNeeded;
|
|
2918
|
+
error.balance = data2?.balance;
|
|
2919
|
+
error.accepts = data2?.accepts;
|
|
2920
|
+
error.response = data2;
|
|
2921
|
+
throw error;
|
|
2922
|
+
}
|
|
2923
|
+
throw uploadErr;
|
|
2924
|
+
}
|
|
2925
|
+
const data = response?.data || {};
|
|
2926
|
+
const cid = data.cid || data.IpfsHash;
|
|
2927
|
+
if (!cid) {
|
|
2928
|
+
throw new Error("Worker response missing cid");
|
|
2929
|
+
}
|
|
2930
|
+
if (warm || config.shouldWarm) await warmGateways(cid, { gateways });
|
|
2931
|
+
return {
|
|
2932
|
+
cid,
|
|
2933
|
+
provider: "worker",
|
|
2934
|
+
response: data
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2937
|
+
async function uploadRawJson(content, options2 = {}) {
|
|
2938
|
+
if (!content || typeof content !== "object") {
|
|
2939
|
+
throw new Error("content object required");
|
|
2940
|
+
}
|
|
2941
|
+
const { providers, provider, warm, gateways, name = "manifest.json" } = options2;
|
|
2942
|
+
const order = resolveProviderOrder(providers || provider);
|
|
2943
|
+
if (!order.length) {
|
|
2944
|
+
throw new Error("No IPFS providers configured");
|
|
2945
|
+
}
|
|
2946
|
+
const errors = [];
|
|
2947
|
+
for (const candidate of order) {
|
|
2948
|
+
try {
|
|
2949
|
+
if (candidate === "worker") {
|
|
2950
|
+
return await uploadRawViaWorker(content, { name, warm, gateways });
|
|
2951
|
+
}
|
|
2952
|
+
if (candidate === "pinata") {
|
|
2953
|
+
if (process.env.SAGE_DEBUG_WORKER === "1") {
|
|
2954
|
+
console.log("[SDK WARN] uploadRawJson falling back to pinata - content will be wrapped");
|
|
2955
|
+
}
|
|
2956
|
+
return await uploadToPinata(content, { filename: name, warm, gateways });
|
|
2957
|
+
}
|
|
2958
|
+
if (candidate === "web3") {
|
|
2959
|
+
if (process.env.SAGE_DEBUG_WORKER === "1") {
|
|
2960
|
+
console.log("[SDK WARN] uploadRawJson falling back to web3 - content will be wrapped");
|
|
2961
|
+
}
|
|
2962
|
+
return await uploadToWeb3Storage(content, { warm, gateways });
|
|
2963
|
+
}
|
|
2964
|
+
if (candidate === "simulate") {
|
|
2965
|
+
return await uploadSimulated(content, { warm, gateways });
|
|
2966
|
+
}
|
|
2967
|
+
} catch (error2) {
|
|
2968
|
+
if (error2.code === "CONTENT_BLOCKED" || error2.code === "PAYMENT_REQUIRED") {
|
|
2969
|
+
throw error2;
|
|
2970
|
+
}
|
|
2971
|
+
errors.push({ provider: candidate, error: error2 });
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
const error = new Error("All IPFS providers failed");
|
|
2975
|
+
error.details = errors;
|
|
2976
|
+
throw error;
|
|
2977
|
+
}
|
|
2523
2978
|
async function uploadToPinata(payload, { filename = "payload.json", metadata, warm, gateways } = {}) {
|
|
2524
2979
|
if (!hasPinataCreds()) {
|
|
2525
2980
|
throw new Error("Missing Pinata credentials");
|
|
@@ -2748,28 +3203,37 @@ var require_ipfs = __commonJS({
|
|
|
2748
3203
|
}
|
|
2749
3204
|
async function recordDiscoveryEvent(event = {}) {
|
|
2750
3205
|
if (!event || typeof event !== "object") throw new Error("event required");
|
|
2751
|
-
const
|
|
2752
|
-
if (!
|
|
2753
|
-
const payload = { ...event,
|
|
2754
|
-
return postWorkerJson("discoveryEventsPath", "/
|
|
3206
|
+
const cid = typeof event.promptId === "string" ? event.promptId.trim() : typeof event.cid === "string" ? event.cid.trim() : "";
|
|
3207
|
+
if (!cid) throw new Error("promptId or cid required");
|
|
3208
|
+
const payload = { ...event, cid };
|
|
3209
|
+
return withRetry(() => postWorkerJson("discoveryEventsPath", "/trends/usage", payload));
|
|
3210
|
+
}
|
|
3211
|
+
async function recordMcpUsageEvent({ promptId, cid, metadata, address } = {}) {
|
|
3212
|
+
const id2 = typeof promptId === "string" ? promptId.trim() : typeof cid === "string" ? cid.trim() : "";
|
|
3213
|
+
if (!id2) throw new Error("promptId or cid required");
|
|
3214
|
+
const payload = { cid: id2 };
|
|
3215
|
+
if (metadata && typeof metadata === "object") payload.metadata = metadata;
|
|
3216
|
+
const actor = address || config.worker.address;
|
|
3217
|
+
if (actor) payload.address = actor;
|
|
3218
|
+
return withRetry(() => postWorkerJson("discoveryMcpPath", "/trends/usage", payload));
|
|
2755
3219
|
}
|
|
2756
|
-
async function
|
|
2757
|
-
const id2 = typeof promptId === "string" ? promptId.trim() : "";
|
|
2758
|
-
if (!id2) throw new Error("promptId required");
|
|
2759
|
-
const payload = {
|
|
3220
|
+
async function recordLaunchEvent({ promptId, cid, metadata, address } = {}) {
|
|
3221
|
+
const id2 = typeof promptId === "string" ? promptId.trim() : typeof cid === "string" ? cid.trim() : "";
|
|
3222
|
+
if (!id2) throw new Error("promptId or cid required");
|
|
3223
|
+
const payload = { cid: id2 };
|
|
2760
3224
|
if (metadata && typeof metadata === "object") payload.metadata = metadata;
|
|
2761
3225
|
const actor = address || config.worker.address;
|
|
2762
3226
|
if (actor) payload.address = actor;
|
|
2763
|
-
return postWorkerJson("
|
|
3227
|
+
return withRetry(() => postWorkerJson("discoveryLaunchPath", "/trends/usage", payload));
|
|
2764
3228
|
}
|
|
2765
|
-
async function
|
|
2766
|
-
const id2 = typeof
|
|
2767
|
-
if (!id2) throw new Error("
|
|
2768
|
-
const payload = {
|
|
3229
|
+
async function recordInstallEvent({ skillCid, cid, metadata, address } = {}) {
|
|
3230
|
+
const id2 = typeof skillCid === "string" ? skillCid.trim() : typeof cid === "string" ? cid.trim() : "";
|
|
3231
|
+
if (!id2) throw new Error("skillCid or cid required");
|
|
3232
|
+
const payload = { cid: id2 };
|
|
2769
3233
|
if (metadata && typeof metadata === "object") payload.metadata = metadata;
|
|
2770
3234
|
const actor = address || config.worker.address;
|
|
2771
3235
|
if (actor) payload.address = actor;
|
|
2772
|
-
return postWorkerJson("
|
|
3236
|
+
return withRetry(() => postWorkerJson("discoveryInstallPath", "/trends/install", payload));
|
|
2773
3237
|
}
|
|
2774
3238
|
async function submitGovernanceReport(payload = {}) {
|
|
2775
3239
|
if (!payload || typeof payload !== "object") throw new Error("payload required");
|
|
@@ -2831,6 +3295,7 @@ var require_ipfs = __commonJS({
|
|
|
2831
3295
|
uploadPrompt,
|
|
2832
3296
|
uploadJson,
|
|
2833
3297
|
uploadText,
|
|
3298
|
+
uploadRawJson,
|
|
2834
3299
|
downloadJson,
|
|
2835
3300
|
warmGateways,
|
|
2836
3301
|
buildGatewayUrls: (cid, extra) => buildGatewayUrls(cid, config.gateway, extra),
|
|
@@ -2838,6 +3303,7 @@ var require_ipfs = __commonJS({
|
|
|
2838
3303
|
recordDiscoveryEvent,
|
|
2839
3304
|
recordMcpUsage: recordMcpUsageEvent,
|
|
2840
3305
|
recordLaunchEvent,
|
|
3306
|
+
recordInstallEvent,
|
|
2841
3307
|
submitGovernanceReport,
|
|
2842
3308
|
listGovernanceReports,
|
|
2843
3309
|
reviewGovernanceReport,
|
|
@@ -2857,7 +3323,7 @@ var require_ipfs = __commonJS({
|
|
|
2857
3323
|
const timeoutMs = Number(options.timeoutMs ?? env.SAGE_IPFS_TIMEOUT_MS ?? 15e3);
|
|
2858
3324
|
const challengePath = ensureLeadingSlash(
|
|
2859
3325
|
options.workerChallengePath || env.SAGE_IPFS_WORKER_CHALLENGE_PATH,
|
|
2860
|
-
"/auth/challenge"
|
|
3326
|
+
"/ipfs/auth/challenge"
|
|
2861
3327
|
);
|
|
2862
3328
|
const warmPath = ensureLeadingSlash(
|
|
2863
3329
|
options.workerWarmPath || env.SAGE_IPFS_WORKER_WARM_PATH,
|
|
@@ -3389,7 +3855,10 @@ var require_library = __commonJS({
|
|
|
3389
3855
|
proposer: getAddress(info.lastUpdater),
|
|
3390
3856
|
promptCount: 0,
|
|
3391
3857
|
// Not tracked on-chain in V4
|
|
3392
|
-
version: info.version
|
|
3858
|
+
version: info.version,
|
|
3859
|
+
nonce: BigInt(info.nonce.toString()),
|
|
3860
|
+
forkedFromDAO: info.forkedFromDAO && info.forkedFromDAO !== "0x0000000000000000000000000000000000000000" ? getAddress(info.forkedFromDAO) : null,
|
|
3861
|
+
sxxxForkFee: BigInt(info.sxxxForkFee.toString())
|
|
3393
3862
|
};
|
|
3394
3863
|
}
|
|
3395
3864
|
async function getManifestInfo({ provider, registry, manifestCID }) {
|
|
@@ -3425,6 +3894,21 @@ var require_library = __commonJS({
|
|
|
3425
3894
|
]);
|
|
3426
3895
|
return { to, data, value: 0n };
|
|
3427
3896
|
}
|
|
3897
|
+
function buildUpdateLibraryCAS({ registry, subdao, manifestCID, version = "1.0.0", expectedNonce }) {
|
|
3898
|
+
const to = normalise(registry, "registry");
|
|
3899
|
+
if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
|
|
3900
|
+
if (expectedNonce === void 0 || expectedNonce === null) {
|
|
3901
|
+
throw new SageSDKError(CODES.INVALID_ARGS, "expectedNonce required for CAS update");
|
|
3902
|
+
}
|
|
3903
|
+
const iface = new Interface(["function updateLibraryCAS(address,string,string,uint256)"]);
|
|
3904
|
+
const data = iface.encodeFunctionData("updateLibraryCAS", [
|
|
3905
|
+
normalise(subdao, "subdao"),
|
|
3906
|
+
String(manifestCID),
|
|
3907
|
+
String(version),
|
|
3908
|
+
BigInt(expectedNonce)
|
|
3909
|
+
]);
|
|
3910
|
+
return { to, data, value: 0n };
|
|
3911
|
+
}
|
|
3428
3912
|
module2.exports = {
|
|
3429
3913
|
listManifests,
|
|
3430
3914
|
getManifestInfo,
|
|
@@ -3432,6 +3916,7 @@ var require_library = __commonJS({
|
|
|
3432
3916
|
getLatestLibrary,
|
|
3433
3917
|
hasScopedOwnership,
|
|
3434
3918
|
buildUpdateLibraryTx,
|
|
3919
|
+
buildUpdateLibraryCAS,
|
|
3435
3920
|
_computeLibraryKey,
|
|
3436
3921
|
getBeforeAfterForUpdate,
|
|
3437
3922
|
/** Build an authorizeTimelock(timelock, subdao) call for a LibraryRegistry. */
|
|
@@ -3450,73 +3935,305 @@ var require_library = __commonJS({
|
|
|
3450
3935
|
const tgt = to ? normalise(to, "to") : reg;
|
|
3451
3936
|
const from = normalise(timelock, "timelock");
|
|
3452
3937
|
try {
|
|
3453
|
-
await provider.call({ to: tgt, data, from });
|
|
3454
|
-
return { ok: true };
|
|
3455
|
-
} catch (err) {
|
|
3456
|
-
return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
|
|
3938
|
+
await provider.call({ to: tgt, data, from });
|
|
3939
|
+
return { ok: true };
|
|
3940
|
+
} catch (err) {
|
|
3941
|
+
return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
|
|
3942
|
+
}
|
|
3943
|
+
},
|
|
3944
|
+
executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, manifestCID = "", version = "1.0.0" }) {
|
|
3945
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
3946
|
+
const payload = buildUpdateLibraryTx({ registry, subdao, manifestCID, version });
|
|
3947
|
+
const sim = await this.simulateAsTimelock({ provider, registry, to: payload.to, data: payload.data, timelock });
|
|
3948
|
+
const out = { ok: !!sim.ok, error: sim.ok ? null : sim.error?.message || "revert", missingRole: null };
|
|
3949
|
+
return out;
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
}
|
|
3953
|
+
});
|
|
3954
|
+
|
|
3955
|
+
// src/governance/index.js
|
|
3956
|
+
var require_governance = __commonJS({
|
|
3957
|
+
"src/governance/index.js"(exports2, module2) {
|
|
3958
|
+
var { Contract, Interface, AbiCoder, ZeroAddress, getAddress, hexlify, keccak256, toUtf8Bytes } = __require("ethers");
|
|
3959
|
+
var ABI = require_abi();
|
|
3960
|
+
var { BigIntZero } = require_types();
|
|
3961
|
+
var { SageSDKError, CODES } = require_errors();
|
|
3962
|
+
var { makeProposalDescription, stripProposalSalt } = require_description();
|
|
3963
|
+
var subgraph = require_subgraph();
|
|
3964
|
+
var adapters = require_transports();
|
|
3965
|
+
var subdao = require_subdao();
|
|
3966
|
+
function normaliseGovernor(governor) {
|
|
3967
|
+
if (!governor) throw new SageSDKError(CODES.INVALID_ARGS, "governor required");
|
|
3968
|
+
try {
|
|
3969
|
+
return getAddress(governor);
|
|
3970
|
+
} catch (err) {
|
|
3971
|
+
throw new SageSDKError(CODES.INVALID_ARGS, "invalid governor address", { cause: err });
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
async function getGovernorInfo({ provider, governor }) {
|
|
3975
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
3976
|
+
const addr = normaliseGovernor(governor);
|
|
3977
|
+
const g = new Contract(addr, ABI.Governor, provider);
|
|
3978
|
+
const results = await Promise.all([
|
|
3979
|
+
g.name().catch(() => null),
|
|
3980
|
+
g.votingDelay().catch(() => null),
|
|
3981
|
+
g.votingPeriod().catch(() => null),
|
|
3982
|
+
g.proposalThreshold().catch(() => null),
|
|
3983
|
+
g.timelock().catch(async () => {
|
|
3984
|
+
try {
|
|
3985
|
+
return await g._executor();
|
|
3986
|
+
} catch {
|
|
3987
|
+
return null;
|
|
3988
|
+
}
|
|
3989
|
+
}),
|
|
3990
|
+
g.sxxxToken().catch(() => null),
|
|
3991
|
+
// Try quorumVotes first (new fixed model), fall back to quorumNumerator (legacy OZ)
|
|
3992
|
+
g.quorumVotes ? g.quorumVotes().catch(() => null) : null,
|
|
3993
|
+
g.quorumNumerator ? g.quorumNumerator().catch(() => null) : null
|
|
3994
|
+
]);
|
|
3995
|
+
const [name, votingDelay, votingPeriod, proposalThreshold, timelockRaw, sxxxToken, quorumVotes, quorumNumerator] = results;
|
|
3996
|
+
const timelock = timelockRaw ? getAddress(timelockRaw) : null;
|
|
3997
|
+
return {
|
|
3998
|
+
address: addr,
|
|
3999
|
+
name,
|
|
4000
|
+
votingDelay,
|
|
4001
|
+
votingPeriod,
|
|
4002
|
+
proposalThreshold,
|
|
4003
|
+
timelock,
|
|
4004
|
+
sxxxToken: sxxxToken && /^0x/.test(String(sxxxToken)) ? getAddress(sxxxToken) : null,
|
|
4005
|
+
// Primary: quorumVotes (fixed absolute amount)
|
|
4006
|
+
quorumVotes,
|
|
4007
|
+
// Legacy alias (deprecated)
|
|
4008
|
+
quorumNumerator: quorumNumerator ?? quorumVotes
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
async function getGovernorTimepointMinusOne({ provider, governor }) {
|
|
4012
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4013
|
+
const govAddr = normaliseGovernor(governor);
|
|
4014
|
+
try {
|
|
4015
|
+
const iface = new Interface(["function clock() view returns (uint48)"]);
|
|
4016
|
+
const data = iface.encodeFunctionData("clock", []);
|
|
4017
|
+
const ret = await provider.call({ to: govAddr, data });
|
|
4018
|
+
if (ret && ret !== "0x") {
|
|
4019
|
+
const [clk] = AbiCoder.defaultAbiCoder().decode(["uint48"], ret);
|
|
4020
|
+
const now = BigInt(clk.toString());
|
|
4021
|
+
return now > 0n ? now - 1n : 0n;
|
|
4022
|
+
}
|
|
4023
|
+
} catch (_) {
|
|
4024
|
+
}
|
|
4025
|
+
try {
|
|
4026
|
+
const latest = await provider.getBlockNumber();
|
|
4027
|
+
const now = BigInt(latest);
|
|
4028
|
+
return now > 0n ? now - 1n : 0n;
|
|
4029
|
+
} catch (_) {
|
|
4030
|
+
}
|
|
4031
|
+
return null;
|
|
4032
|
+
}
|
|
4033
|
+
async function getGovernorVotesAt({ provider, governor, account, timepoint }) {
|
|
4034
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4035
|
+
if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
|
|
4036
|
+
if (timepoint == null) throw new SageSDKError(CODES.INVALID_ARGS, "timepoint required");
|
|
4037
|
+
const govAddr = normaliseGovernor(governor);
|
|
4038
|
+
const acct = getAddress(account);
|
|
4039
|
+
const tp = typeof timepoint === "bigint" ? timepoint : BigInt(String(timepoint));
|
|
4040
|
+
try {
|
|
4041
|
+
const iface = new Interface(["function getVotes(address,uint256) view returns (uint256)"]);
|
|
4042
|
+
const data = iface.encodeFunctionData("getVotes", [acct, tp]);
|
|
4043
|
+
const ret = await provider.call({ to: govAddr, data });
|
|
4044
|
+
if (ret && ret !== "0x") {
|
|
4045
|
+
const [val] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
|
|
4046
|
+
return BigInt(val.toString());
|
|
4047
|
+
}
|
|
4048
|
+
} catch (_) {
|
|
4049
|
+
}
|
|
4050
|
+
try {
|
|
4051
|
+
const { votingToken } = await resolveVotesTokenChain({ provider, governor: govAddr });
|
|
4052
|
+
const tokenAddr = getAddress(votingToken);
|
|
4053
|
+
const iface = new Interface(["function getPastVotes(address,uint256) view returns (uint256)"]);
|
|
4054
|
+
const data = iface.encodeFunctionData("getPastVotes", [acct, tp]);
|
|
4055
|
+
const ret = await provider.call({ to: tokenAddr, data });
|
|
4056
|
+
if (ret && ret !== "0x") {
|
|
4057
|
+
const [val] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
|
|
4058
|
+
return BigInt(val.toString());
|
|
4059
|
+
}
|
|
4060
|
+
} catch (_) {
|
|
4061
|
+
}
|
|
4062
|
+
return null;
|
|
4063
|
+
}
|
|
4064
|
+
async function getProposalThresholdStatus({ provider, governor, proposer }) {
|
|
4065
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4066
|
+
if (!proposer) throw new SageSDKError(CODES.INVALID_ARGS, "proposer required");
|
|
4067
|
+
const govAddr = normaliseGovernor(governor);
|
|
4068
|
+
const user = getAddress(proposer);
|
|
4069
|
+
const g = new Contract(govAddr, ABI.Governor, provider);
|
|
4070
|
+
const [threshold, sxxxTokenAddr] = await Promise.all([
|
|
4071
|
+
g.proposalThreshold().catch(() => null),
|
|
4072
|
+
g.sxxxToken().catch(() => null)
|
|
4073
|
+
]);
|
|
4074
|
+
const proposalThreshold = threshold != null ? BigInt(threshold.toString()) : null;
|
|
4075
|
+
const sxxxToken = sxxxTokenAddr && /^0x[0-9a-fA-F]{40}$/.test(String(sxxxTokenAddr)) ? getAddress(sxxxTokenAddr) : null;
|
|
4076
|
+
const votesTimepoint = await getGovernorTimepointMinusOne({ provider, governor: govAddr });
|
|
4077
|
+
const proposerVotes = votesTimepoint != null ? await getGovernorVotesAt({ provider, governor: govAddr, account: user, timepoint: votesTimepoint }) : null;
|
|
4078
|
+
let sxxxBalance = null;
|
|
4079
|
+
if (sxxxToken) {
|
|
4080
|
+
try {
|
|
4081
|
+
const iface = new Interface(["function balanceOf(address) view returns (uint256)"]);
|
|
4082
|
+
const data = iface.encodeFunctionData("balanceOf", [user]);
|
|
4083
|
+
const ret = await provider.call({ to: sxxxToken, data });
|
|
4084
|
+
if (ret && ret !== "0x") {
|
|
4085
|
+
const [bal] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
|
|
4086
|
+
sxxxBalance = BigInt(bal.toString());
|
|
4087
|
+
}
|
|
4088
|
+
} catch (_) {
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
const thresholdMet = proposalThreshold != null && proposerVotes != null ? proposerVotes >= proposalThreshold : null;
|
|
4092
|
+
let cooldownRemaining = null;
|
|
4093
|
+
let canPropose = null;
|
|
4094
|
+
try {
|
|
4095
|
+
const iface = new Interface(["function getProposalCooldownInfo(address) view returns (bool canPropose, uint256 cooldownRemaining)"]);
|
|
4096
|
+
const data = iface.encodeFunctionData("getProposalCooldownInfo", [user]);
|
|
4097
|
+
const ret = await provider.call({ to: govAddr, data });
|
|
4098
|
+
if (ret && ret !== "0x") {
|
|
4099
|
+
const decoded = AbiCoder.defaultAbiCoder().decode(["bool", "uint256"], ret);
|
|
4100
|
+
canPropose = decoded[0];
|
|
4101
|
+
cooldownRemaining = BigInt(decoded[1].toString());
|
|
4102
|
+
}
|
|
4103
|
+
} catch (_) {
|
|
4104
|
+
try {
|
|
4105
|
+
const cooldownIface = new Interface(["function proposalCooldown() view returns (uint256)"]);
|
|
4106
|
+
const lastIface = new Interface(["function lastProposalTimestamp(address) view returns (uint256)"]);
|
|
4107
|
+
const [cooldownRet, lastRet] = await Promise.all([
|
|
4108
|
+
provider.call({ to: govAddr, data: cooldownIface.encodeFunctionData("proposalCooldown", []) }).catch(() => null),
|
|
4109
|
+
provider.call({ to: govAddr, data: lastIface.encodeFunctionData("lastProposalTimestamp", [user]) }).catch(() => null)
|
|
4110
|
+
]);
|
|
4111
|
+
if (cooldownRet && cooldownRet !== "0x" && lastRet && lastRet !== "0x") {
|
|
4112
|
+
const [cooldown] = AbiCoder.defaultAbiCoder().decode(["uint256"], cooldownRet);
|
|
4113
|
+
const [last] = AbiCoder.defaultAbiCoder().decode(["uint256"], lastRet);
|
|
4114
|
+
const block = await provider.getBlock("latest").catch(() => null);
|
|
4115
|
+
if (block && block.timestamp != null) {
|
|
4116
|
+
const now = BigInt(block.timestamp);
|
|
4117
|
+
const endsAt = BigInt(last.toString()) + BigInt(cooldown.toString());
|
|
4118
|
+
cooldownRemaining = endsAt > now ? endsAt - now : 0n;
|
|
4119
|
+
canPropose = cooldownRemaining === 0n;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
} catch (_2) {
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
const canProposeOverall = thresholdMet === true && (canPropose === true || canPropose === null);
|
|
4126
|
+
return {
|
|
4127
|
+
governor: govAddr,
|
|
4128
|
+
proposer: user,
|
|
4129
|
+
proposalThreshold,
|
|
4130
|
+
sxxxToken,
|
|
4131
|
+
sxxxBalance,
|
|
4132
|
+
proposerVotes,
|
|
4133
|
+
votesTimepoint,
|
|
4134
|
+
thresholdMet,
|
|
4135
|
+
cooldownRemaining,
|
|
4136
|
+
cooldownElapsed: cooldownRemaining != null ? cooldownRemaining === 0n : null,
|
|
4137
|
+
canPropose: canProposeOverall
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
async function getProposalCancelability({ provider, governor, proposalId }) {
|
|
4141
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4142
|
+
if (proposalId == null) throw new SageSDKError(CODES.INVALID_ARGS, "proposalId required");
|
|
4143
|
+
const govAddr = normaliseGovernor(governor);
|
|
4144
|
+
const pid = typeof proposalId === "bigint" ? proposalId : String(proposalId).startsWith("0x") ? BigInt(proposalId) : BigInt(String(proposalId));
|
|
4145
|
+
let proposalProposer = null;
|
|
4146
|
+
try {
|
|
4147
|
+
const iface = new Interface(["function proposalProposer(uint256) view returns (address)"]);
|
|
4148
|
+
const data = iface.encodeFunctionData("proposalProposer", [pid]);
|
|
4149
|
+
const ret = await provider.call({ to: govAddr, data });
|
|
4150
|
+
if (ret && ret !== "0x") {
|
|
4151
|
+
const [addr] = AbiCoder.defaultAbiCoder().decode(["address"], ret);
|
|
4152
|
+
if (addr && /^0x[0-9a-fA-F]{40}$/.test(String(addr)) && addr !== ZeroAddress) {
|
|
4153
|
+
proposalProposer = getAddress(addr);
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
} catch (_) {
|
|
4157
|
+
}
|
|
4158
|
+
let proposalCreator = null;
|
|
4159
|
+
if (proposalProposer) {
|
|
4160
|
+
proposalCreator = proposalProposer;
|
|
4161
|
+
} else {
|
|
4162
|
+
try {
|
|
4163
|
+
const iface = new Interface(["function proposalCreator(uint256) view returns (address)"]);
|
|
4164
|
+
const data = iface.encodeFunctionData("proposalCreator", [pid]);
|
|
4165
|
+
const ret = await provider.call({ to: govAddr, data });
|
|
4166
|
+
if (ret && ret !== "0x") {
|
|
4167
|
+
const [addr] = AbiCoder.defaultAbiCoder().decode(["address"], ret);
|
|
4168
|
+
if (addr && /^0x[0-9a-fA-F]{40}$/.test(String(addr)) && addr !== ZeroAddress) {
|
|
4169
|
+
proposalCreator = getAddress(addr);
|
|
4170
|
+
proposalProposer = proposalCreator;
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
} catch (_) {
|
|
3457
4174
|
}
|
|
3458
|
-
},
|
|
3459
|
-
executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, manifestCID = "", version = "1.0.0" }) {
|
|
3460
|
-
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
3461
|
-
const payload = buildUpdateLibraryTx({ registry, subdao, manifestCID, version });
|
|
3462
|
-
const sim = await this.simulateAsTimelock({ provider, registry, to: payload.to, data: payload.data, timelock });
|
|
3463
|
-
const out = { ok: !!sim.ok, error: sim.ok ? null : sim.error?.message || "revert", missingRole: null };
|
|
3464
|
-
return out;
|
|
3465
4175
|
}
|
|
3466
|
-
|
|
3467
|
-
}
|
|
3468
|
-
});
|
|
3469
|
-
|
|
3470
|
-
// src/governance/index.js
|
|
3471
|
-
var require_governance = __commonJS({
|
|
3472
|
-
"src/governance/index.js"(exports2, module2) {
|
|
3473
|
-
var { Contract, Interface, AbiCoder, getAddress, hexlify, keccak256, toUtf8Bytes } = __require("ethers");
|
|
3474
|
-
var ABI = require_abi();
|
|
3475
|
-
var { BigIntZero } = require_types();
|
|
3476
|
-
var { SageSDKError, CODES } = require_errors();
|
|
3477
|
-
var { makeProposalDescription, stripProposalSalt } = require_description();
|
|
3478
|
-
var subgraph = require_subgraph();
|
|
3479
|
-
var adapters = require_transports();
|
|
3480
|
-
function normaliseGovernor(governor) {
|
|
3481
|
-
if (!governor) throw new SageSDKError(CODES.INVALID_ARGS, "governor required");
|
|
4176
|
+
let proposalThresholdSnapshot = null;
|
|
3482
4177
|
try {
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
4178
|
+
const iface = new Interface(["function proposalThresholdSnapshot(uint256) view returns (uint256)"]);
|
|
4179
|
+
const data = iface.encodeFunctionData("proposalThresholdSnapshot", [pid]);
|
|
4180
|
+
const ret = await provider.call({ to: govAddr, data });
|
|
4181
|
+
if (ret && ret !== "0x") {
|
|
4182
|
+
const [val] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
|
|
4183
|
+
proposalThresholdSnapshot = BigInt(val.toString());
|
|
4184
|
+
}
|
|
4185
|
+
} catch (_) {
|
|
3486
4186
|
}
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
const addr = normaliseGovernor(governor);
|
|
3491
|
-
const g = new Contract(addr, ABI.Governor, provider);
|
|
3492
|
-
const results = await Promise.all([
|
|
3493
|
-
g.name().catch(() => null),
|
|
3494
|
-
g.votingDelay().catch(() => null),
|
|
3495
|
-
g.votingPeriod().catch(() => null),
|
|
4187
|
+
const g = new Contract(govAddr, ABI.Governor, provider);
|
|
4188
|
+
const [state, threshold, sxxxTokenAddr] = await Promise.all([
|
|
4189
|
+
g.state(pid).catch(() => null),
|
|
3496
4190
|
g.proposalThreshold().catch(() => null),
|
|
3497
|
-
g.
|
|
3498
|
-
try {
|
|
3499
|
-
return await g._executor();
|
|
3500
|
-
} catch {
|
|
3501
|
-
return null;
|
|
3502
|
-
}
|
|
3503
|
-
}),
|
|
3504
|
-
g.sxxxToken().catch(() => null),
|
|
3505
|
-
g.quorumNumerator ? g.quorumNumerator().catch(() => null) : null,
|
|
3506
|
-
g.stakeAmount ? g.stakeAmount().catch(() => null) : null
|
|
4191
|
+
g.sxxxToken().catch(() => null)
|
|
3507
4192
|
]);
|
|
3508
|
-
const
|
|
3509
|
-
const
|
|
4193
|
+
const proposalState = state != null ? Number(state) : null;
|
|
4194
|
+
const proposalThresholdCurrent = threshold != null ? BigInt(threshold.toString()) : null;
|
|
4195
|
+
const sxxxToken = sxxxTokenAddr && /^0x[0-9a-fA-F]{40}$/.test(String(sxxxTokenAddr)) ? getAddress(sxxxTokenAddr) : null;
|
|
4196
|
+
const cancelThreshold = proposalThresholdSnapshot != null ? proposalThresholdSnapshot : proposalThresholdCurrent;
|
|
4197
|
+
const votesTimepoint = await getGovernorTimepointMinusOne({ provider, governor: govAddr });
|
|
4198
|
+
const proposerVotes = proposalProposer && votesTimepoint != null ? await getGovernorVotesAt({ provider, governor: govAddr, account: proposalProposer, timepoint: votesTimepoint }) : null;
|
|
4199
|
+
let proposerBalance = null;
|
|
4200
|
+
if (proposalProposer && sxxxToken) {
|
|
4201
|
+
try {
|
|
4202
|
+
const iface = new Interface(["function balanceOf(address) view returns (uint256)"]);
|
|
4203
|
+
const data = iface.encodeFunctionData("balanceOf", [proposalProposer]);
|
|
4204
|
+
const ret = await provider.call({ to: sxxxToken, data });
|
|
4205
|
+
if (ret && ret !== "0x") {
|
|
4206
|
+
const [bal] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
|
|
4207
|
+
proposerBalance = BigInt(bal.toString());
|
|
4208
|
+
}
|
|
4209
|
+
} catch (_) {
|
|
4210
|
+
}
|
|
4211
|
+
}
|
|
4212
|
+
const cancelableStates = /* @__PURE__ */ new Set([0, 1]);
|
|
4213
|
+
const isInCancelableState = proposalState != null && cancelableStates.has(proposalState);
|
|
4214
|
+
const proposerBelowThreshold = cancelThreshold != null && proposerVotes != null ? proposerVotes < cancelThreshold : null;
|
|
4215
|
+
const isCancelableByAnyone = isInCancelableState && proposerBelowThreshold === true;
|
|
3510
4216
|
return {
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
4217
|
+
governor: govAddr,
|
|
4218
|
+
proposalId: pid,
|
|
4219
|
+
proposalProposer,
|
|
4220
|
+
proposalCreator,
|
|
4221
|
+
proposalState,
|
|
4222
|
+
proposalThresholdSnapshot,
|
|
4223
|
+
proposalThresholdCurrent,
|
|
4224
|
+
cancelThreshold,
|
|
4225
|
+
sxxxToken,
|
|
4226
|
+
proposerBalance,
|
|
4227
|
+
votesTimepoint,
|
|
4228
|
+
proposerVotes,
|
|
4229
|
+
proposerBelowThreshold,
|
|
4230
|
+
isInCancelableState,
|
|
4231
|
+
isCancelableByAnyone,
|
|
4232
|
+
cancelReason: isCancelableByAnyone ? "Proposer voting power dropped below proposal threshold" : isInCancelableState ? "Only proposer can cancel (above threshold)" : "Proposal not in cancelable state",
|
|
4233
|
+
// Backward-compat aliases
|
|
4234
|
+
proposalThreshold: cancelThreshold,
|
|
4235
|
+
creatorBalance: proposerBalance,
|
|
4236
|
+
creatorBelowThreshold: proposerBelowThreshold
|
|
3520
4237
|
};
|
|
3521
4238
|
}
|
|
3522
4239
|
async function getProposal({ provider, governor, id: id2 }) {
|
|
@@ -3713,6 +4430,7 @@ var require_governance = __commonJS({
|
|
|
3713
4430
|
}
|
|
3714
4431
|
var buildQueueTx = (args) => cueTx("queue(address[],uint256[],bytes[],bytes32)", args);
|
|
3715
4432
|
var buildExecuteTx = (args) => cueTx("execute(address[],uint256[],bytes[],bytes32)", args);
|
|
4433
|
+
var buildCancelTx = (args) => cueTx("cancel(address[],uint256[],bytes[],bytes32)", args);
|
|
3716
4434
|
function buildQueueByIdTx({ governor, proposalId }) {
|
|
3717
4435
|
const addr = normaliseGovernor(governor);
|
|
3718
4436
|
const iface = new Interface(ABI.Governor);
|
|
@@ -3859,6 +4577,47 @@ var require_governance = __commonJS({
|
|
|
3859
4577
|
}
|
|
3860
4578
|
return 0n;
|
|
3861
4579
|
}
|
|
4580
|
+
async function getVotesLatestMinusOneWithTimepoint({ provider, token, governor, account }) {
|
|
4581
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4582
|
+
if (!token && !governor) throw new SageSDKError(CODES.INVALID_ARGS, "token or governor required");
|
|
4583
|
+
if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
|
|
4584
|
+
let addr;
|
|
4585
|
+
if (token) {
|
|
4586
|
+
addr = getAddress(token);
|
|
4587
|
+
} else {
|
|
4588
|
+
const govAddr = normaliseGovernor(governor);
|
|
4589
|
+
try {
|
|
4590
|
+
const chain = await resolveVotesTokenChain({ provider, governor: govAddr });
|
|
4591
|
+
addr = getAddress(chain.votingToken);
|
|
4592
|
+
} catch (err) {
|
|
4593
|
+
throw new SageSDKError(CODES.NOT_FOUND, "failed to resolve votes token from governor", { cause: err });
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
const user = getAddress(account);
|
|
4597
|
+
const latest = await provider.getBlockNumber();
|
|
4598
|
+
const timepoint = latest > 0 ? BigInt(latest - 1) : 0n;
|
|
4599
|
+
try {
|
|
4600
|
+
const iface = new Interface(["function getPastVotes(address,uint256) view returns (uint256)"]);
|
|
4601
|
+
const data = iface.encodeFunctionData("getPastVotes", [user, timepoint]);
|
|
4602
|
+
const ret = await provider.call({ to: addr, data });
|
|
4603
|
+
if (ret && ret !== "0x") {
|
|
4604
|
+
const [val] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
|
|
4605
|
+
return { votes: BigInt(val.toString()), timepoint };
|
|
4606
|
+
}
|
|
4607
|
+
} catch (_) {
|
|
4608
|
+
}
|
|
4609
|
+
try {
|
|
4610
|
+
const iface2 = new Interface(["function getVotes(address) view returns (uint256)"]);
|
|
4611
|
+
const d2 = iface2.encodeFunctionData("getVotes", [user]);
|
|
4612
|
+
const ret2 = await provider.call({ to: addr, data: d2 });
|
|
4613
|
+
if (ret2 && ret2 !== "0x") {
|
|
4614
|
+
const [val2] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret2);
|
|
4615
|
+
return { votes: BigInt(val2.toString()), timepoint };
|
|
4616
|
+
}
|
|
4617
|
+
} catch (_) {
|
|
4618
|
+
}
|
|
4619
|
+
return { votes: 0n, timepoint };
|
|
4620
|
+
}
|
|
3862
4621
|
async function simulatePropose({ provider, governor, targets = [], values = [], calldatas = [], description = "", sender }) {
|
|
3863
4622
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
3864
4623
|
const addr = normaliseGovernor(governor);
|
|
@@ -3908,13 +4667,18 @@ var require_governance = __commonJS({
|
|
|
3908
4667
|
async function buildDelegateSelfTx({ provider, governor, account }) {
|
|
3909
4668
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
3910
4669
|
const govAddr = normaliseGovernor(governor);
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
4670
|
+
try {
|
|
4671
|
+
const { baseToken } = await resolveVotesTokenChain({ provider, governor: govAddr });
|
|
4672
|
+
return buildDelegateTx({ token: baseToken, delegatee: account });
|
|
4673
|
+
} catch (_) {
|
|
4674
|
+
const g = new Contract(govAddr, ABI.Governor, provider);
|
|
4675
|
+
const token = await g.sxxxToken();
|
|
4676
|
+
return buildDelegateTx({ token, delegatee: account });
|
|
4677
|
+
}
|
|
3914
4678
|
}
|
|
3915
|
-
async function resolveVotesTokenChain({ provider, subdao, governor }) {
|
|
4679
|
+
async function resolveVotesTokenChain({ provider, subdao: subdao2, governor }) {
|
|
3916
4680
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
3917
|
-
if (!
|
|
4681
|
+
if (!subdao2 && !governor) throw new SageSDKError(CODES.INVALID_ARGS, "subdao or governor required");
|
|
3918
4682
|
let votingToken = null;
|
|
3919
4683
|
if (governor) {
|
|
3920
4684
|
const govAddr = normaliseGovernor(governor);
|
|
@@ -3941,19 +4705,6 @@ var require_governance = __commonJS({
|
|
|
3941
4705
|
}
|
|
3942
4706
|
}
|
|
3943
4707
|
}
|
|
3944
|
-
if (!votingToken && subdao) {
|
|
3945
|
-
try {
|
|
3946
|
-
const subAddr = getAddress(subdao);
|
|
3947
|
-
const iSub = new Interface(["function stakeToken() view returns (address)"]);
|
|
3948
|
-
const d3 = iSub.encodeFunctionData("stakeToken", []);
|
|
3949
|
-
const r3 = await provider.call({ to: subAddr, data: d3 });
|
|
3950
|
-
if (r3 && r3 !== "0x") {
|
|
3951
|
-
const [addr] = AbiCoder.defaultAbiCoder().decode(["address"], r3);
|
|
3952
|
-
votingToken = addr;
|
|
3953
|
-
}
|
|
3954
|
-
} catch (_) {
|
|
3955
|
-
}
|
|
3956
|
-
}
|
|
3957
4708
|
if (!votingToken) {
|
|
3958
4709
|
throw new SageSDKError(CODES.NOT_FOUND, "failed to resolve votes token");
|
|
3959
4710
|
}
|
|
@@ -3974,7 +4725,7 @@ var require_governance = __commonJS({
|
|
|
3974
4725
|
const [decodedDao] = AbiCoder.defaultAbiCoder().decode(["address"], rawDao);
|
|
3975
4726
|
const baseNorm = getAddress(decodedBase);
|
|
3976
4727
|
const daoNorm = getAddress(decodedDao);
|
|
3977
|
-
if (!
|
|
4728
|
+
if (!subdao2 || daoNorm === getAddress(subdao2)) {
|
|
3978
4729
|
baseToken = baseNorm;
|
|
3979
4730
|
isWrapper = true;
|
|
3980
4731
|
}
|
|
@@ -3983,8 +4734,8 @@ var require_governance = __commonJS({
|
|
|
3983
4734
|
}
|
|
3984
4735
|
return { votingToken: votingTokenNorm, baseToken, isWrapper };
|
|
3985
4736
|
}
|
|
3986
|
-
async function describeVotesToken({ provider, subdao, governor }) {
|
|
3987
|
-
const { votingToken, baseToken, isWrapper } = await resolveVotesTokenChain({ provider, subdao, governor });
|
|
4737
|
+
async function describeVotesToken({ provider, subdao: subdao2, governor }) {
|
|
4738
|
+
const { votingToken, baseToken, isWrapper } = await resolveVotesTokenChain({ provider, subdao: subdao2, governor });
|
|
3988
4739
|
let multiplierNFT = null;
|
|
3989
4740
|
let basis = null;
|
|
3990
4741
|
if (isWrapper) {
|
|
@@ -4005,8 +4756,8 @@ var require_governance = __commonJS({
|
|
|
4005
4756
|
} else {
|
|
4006
4757
|
const basisStr = basisBig != null ? basisBig.toString() : "10000";
|
|
4007
4758
|
description = `Voting token = MultipliedVotes(base=${baseNorm}, multiplierNFT=${multiplierNorm || "unknown"}, BASIS=${basisStr})`;
|
|
4008
|
-
if (
|
|
4009
|
-
description += ` for DAO ${getAddress(
|
|
4759
|
+
if (subdao2) {
|
|
4760
|
+
description += ` for DAO ${getAddress(subdao2)}`;
|
|
4010
4761
|
}
|
|
4011
4762
|
}
|
|
4012
4763
|
return {
|
|
@@ -4018,9 +4769,9 @@ var require_governance = __commonJS({
|
|
|
4018
4769
|
description
|
|
4019
4770
|
};
|
|
4020
4771
|
}
|
|
4021
|
-
async function getVotingStatus({ provider, subdao, governor, account }) {
|
|
4772
|
+
async function getVotingStatus({ provider, subdao: subdao2, governor, account }) {
|
|
4022
4773
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4023
|
-
if (!governor && !
|
|
4774
|
+
if (!governor && !subdao2) throw new SageSDKError(CODES.INVALID_ARGS, "governor or subdao required");
|
|
4024
4775
|
if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
|
|
4025
4776
|
const acct = getAddress(account);
|
|
4026
4777
|
let govAddr = null;
|
|
@@ -4028,8 +4779,8 @@ var require_governance = __commonJS({
|
|
|
4028
4779
|
if (governor) {
|
|
4029
4780
|
govAddr = normaliseGovernor(governor);
|
|
4030
4781
|
}
|
|
4031
|
-
if (
|
|
4032
|
-
subdaoAddr = getAddress(
|
|
4782
|
+
if (subdao2) {
|
|
4783
|
+
subdaoAddr = getAddress(subdao2);
|
|
4033
4784
|
if (!govAddr) {
|
|
4034
4785
|
try {
|
|
4035
4786
|
const sub = new Contract(subdaoAddr, ["function governor() view returns (address)"], provider);
|
|
@@ -4047,17 +4798,6 @@ var require_governance = __commonJS({
|
|
|
4047
4798
|
} catch (_) {
|
|
4048
4799
|
}
|
|
4049
4800
|
const desc = await describeVotesToken({ provider, subdao: subdaoAddr || void 0, governor: govAddr });
|
|
4050
|
-
let stakeToken = null;
|
|
4051
|
-
if (subdaoAddr) {
|
|
4052
|
-
try {
|
|
4053
|
-
const sub = new Contract(subdaoAddr, ["function stakeToken() view returns (address)"], provider);
|
|
4054
|
-
const st = await sub.stakeToken();
|
|
4055
|
-
if (st && /^0x[0-9a-fA-F]{40}$/.test(String(st))) {
|
|
4056
|
-
stakeToken = getAddress(st);
|
|
4057
|
-
}
|
|
4058
|
-
} catch (_) {
|
|
4059
|
-
}
|
|
4060
|
-
}
|
|
4061
4801
|
const VotesABI = [
|
|
4062
4802
|
"function getVotes(address) view returns (uint256)",
|
|
4063
4803
|
"function delegates(address) view returns (address)",
|
|
@@ -4077,28 +4817,9 @@ var require_governance = __commonJS({
|
|
|
4077
4817
|
} catch (_) {
|
|
4078
4818
|
}
|
|
4079
4819
|
const canPropose = threshold != null && votingPower != null ? votingPower >= threshold : null;
|
|
4080
|
-
let stakeTokenVotes = null;
|
|
4081
|
-
let stakeTokenDelegate = null;
|
|
4082
|
-
let stakeTokenBalance = null;
|
|
4083
|
-
const tokenMismatch = !!stakeToken && stakeToken.toLowerCase() !== desc.votingToken.toLowerCase();
|
|
4084
|
-
if (stakeToken && tokenMismatch) {
|
|
4085
|
-
try {
|
|
4086
|
-
const stakeC = new Contract(stakeToken, VotesABI, provider);
|
|
4087
|
-
const sv = await stakeC.getVotes(acct).catch(() => null);
|
|
4088
|
-
if (sv != null) stakeTokenVotes = BigInt(sv.toString());
|
|
4089
|
-
const sd = await stakeC.delegates(acct).catch(() => null);
|
|
4090
|
-
if (sd && /^0x[0-9a-fA-F]{40}$/.test(String(sd))) {
|
|
4091
|
-
stakeTokenDelegate = getAddress(sd);
|
|
4092
|
-
}
|
|
4093
|
-
const sb = await stakeC.balanceOf(acct).catch(() => null);
|
|
4094
|
-
if (sb != null) stakeTokenBalance = BigInt(sb.toString());
|
|
4095
|
-
} catch (_) {
|
|
4096
|
-
}
|
|
4097
|
-
}
|
|
4098
4820
|
return {
|
|
4099
4821
|
subdao: subdaoAddr,
|
|
4100
4822
|
governor: govAddr,
|
|
4101
|
-
stakeToken,
|
|
4102
4823
|
votingToken: desc.votingToken,
|
|
4103
4824
|
baseToken: desc.baseToken,
|
|
4104
4825
|
isWrapper: desc.isWrapper,
|
|
@@ -4109,10 +4830,6 @@ var require_governance = __commonJS({
|
|
|
4109
4830
|
tokenBalance,
|
|
4110
4831
|
delegate,
|
|
4111
4832
|
canPropose,
|
|
4112
|
-
tokenMismatch,
|
|
4113
|
-
stakeTokenVotes,
|
|
4114
|
-
stakeTokenDelegate,
|
|
4115
|
-
stakeTokenBalance,
|
|
4116
4833
|
description: desc.description
|
|
4117
4834
|
};
|
|
4118
4835
|
}
|
|
@@ -4126,6 +4843,7 @@ var require_governance = __commonJS({
|
|
|
4126
4843
|
buildCastVoteTx,
|
|
4127
4844
|
buildQueueTx,
|
|
4128
4845
|
buildExecuteTx,
|
|
4846
|
+
buildCancelTx,
|
|
4129
4847
|
buildQueueByIdTx,
|
|
4130
4848
|
buildExecuteByIdTx,
|
|
4131
4849
|
decodeProposalEffects,
|
|
@@ -4138,16 +4856,19 @@ var require_governance = __commonJS({
|
|
|
4138
4856
|
buildDelegateSelfTx,
|
|
4139
4857
|
computeProposalIdHex,
|
|
4140
4858
|
getVotesLatestMinusOne,
|
|
4859
|
+
getVotesLatestMinusOneWithTimepoint,
|
|
4141
4860
|
simulatePropose,
|
|
4861
|
+
// Threshold model (PromptGovernor)
|
|
4862
|
+
getProposalThresholdStatus,
|
|
4863
|
+
getProposalCancelability,
|
|
4142
4864
|
/**
|
|
4143
4865
|
* Resolve the IVotes token used for governance voting.
|
|
4144
4866
|
* Preference order:
|
|
4145
4867
|
* - Governor.token() (if exposed)
|
|
4146
4868
|
* - Governor.sxxxToken()
|
|
4147
|
-
* - SubDAO.stakeToken() (when subdao provided)
|
|
4148
4869
|
*/
|
|
4149
|
-
resolveVotesToken: async function resolveVotesToken({ provider, subdao, governor }) {
|
|
4150
|
-
const { votingToken } = await resolveVotesTokenChain({ provider, subdao, governor });
|
|
4870
|
+
resolveVotesToken: async function resolveVotesToken({ provider, subdao: subdao2, governor }) {
|
|
4871
|
+
const { votingToken } = await resolveVotesTokenChain({ provider, subdao: subdao2, governor });
|
|
4151
4872
|
return votingToken;
|
|
4152
4873
|
},
|
|
4153
4874
|
/**
|
|
@@ -4164,20 +4885,20 @@ var require_governance = __commonJS({
|
|
|
4164
4885
|
*/
|
|
4165
4886
|
getVotingStatus,
|
|
4166
4887
|
/** Build a delegate(self) tx using the preferred votes token resolution path. */
|
|
4167
|
-
buildDelegateSelfPreferred: async function buildDelegateSelfPreferred({ provider, subdao, governor, account }) {
|
|
4888
|
+
buildDelegateSelfPreferred: async function buildDelegateSelfPreferred({ provider, subdao: subdao2, governor, account }) {
|
|
4168
4889
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4169
4890
|
if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
|
|
4170
|
-
const { baseToken } = await resolveVotesTokenChain({ provider, subdao, governor });
|
|
4891
|
+
const { baseToken } = await resolveVotesTokenChain({ provider, subdao: subdao2, governor });
|
|
4171
4892
|
return buildDelegateTx({ token: baseToken, delegatee: account });
|
|
4172
4893
|
},
|
|
4173
4894
|
/**
|
|
4174
4895
|
* Send a delegate(self) and verify votes at latest-1.
|
|
4175
4896
|
* If signer is omitted, returns payload and current votes without sending.
|
|
4176
4897
|
*/
|
|
4177
|
-
delegateSelfAndVerify: async function delegateSelfAndVerify({ provider, subdao, governor, account, signer = null, minVotes = null }) {
|
|
4898
|
+
delegateSelfAndVerify: async function delegateSelfAndVerify({ provider, subdao: subdao2, governor, account, signer = null, minVotes = null }) {
|
|
4178
4899
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4179
4900
|
if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
|
|
4180
|
-
const { votingToken, baseToken } = await resolveVotesTokenChain({ provider, subdao, governor });
|
|
4901
|
+
const { votingToken, baseToken } = await resolveVotesTokenChain({ provider, subdao: subdao2, governor });
|
|
4181
4902
|
const payload = buildDelegateTx({ token: baseToken, delegatee: account });
|
|
4182
4903
|
let txHash = null;
|
|
4183
4904
|
if (signer && typeof signer.sendTransaction === "function") {
|
|
@@ -4190,7 +4911,8 @@ var require_governance = __commonJS({
|
|
|
4190
4911
|
return { txHash, ok, votes, payload };
|
|
4191
4912
|
},
|
|
4192
4913
|
/**
|
|
4193
|
-
* Ensure propose gates based on
|
|
4914
|
+
* Ensure propose gates based on THRESHOLD MODEL (votes-based threshold + cooldown).
|
|
4915
|
+
* No stake or allowance checks - proposer just needs sufficient voting power at clock()-1.
|
|
4194
4916
|
* Returns a non-throwing summary.
|
|
4195
4917
|
*/
|
|
4196
4918
|
ensureProposeGates: async function ensureProposeGates({ provider, governor, proposer }) {
|
|
@@ -4198,90 +4920,488 @@ var require_governance = __commonJS({
|
|
|
4198
4920
|
const govAddr = normaliseGovernor(governor);
|
|
4199
4921
|
const user = getAddress(proposer);
|
|
4200
4922
|
const g = new Contract(govAddr, ABI.Governor, provider);
|
|
4201
|
-
const
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4923
|
+
const status = await getProposalThresholdStatus({ provider, governor: govAddr, proposer: user });
|
|
4924
|
+
return {
|
|
4925
|
+
// Core threshold model
|
|
4926
|
+
threshold: status.proposalThreshold,
|
|
4927
|
+
sxxxBalance: status.sxxxBalance,
|
|
4928
|
+
thresholdMet: status.thresholdMet,
|
|
4929
|
+
cooldownRemaining: status.cooldownRemaining,
|
|
4930
|
+
cooldownElapsed: status.cooldownElapsed,
|
|
4931
|
+
canPropose: status.canPropose,
|
|
4932
|
+
// Voting power (canonical: clock()-1)
|
|
4933
|
+
votesTimepoint: status.votesTimepoint,
|
|
4934
|
+
votingPower: status.proposerVotes,
|
|
4935
|
+
votesOk: status.thresholdMet,
|
|
4936
|
+
// Details
|
|
4937
|
+
details: { sxxxToken: status.sxxxToken, proposer: user, governor: govAddr },
|
|
4938
|
+
cooldown: status.cooldownRemaining
|
|
4939
|
+
// alias for cooldownRemaining
|
|
4940
|
+
};
|
|
4941
|
+
},
|
|
4942
|
+
/**
|
|
4943
|
+
* Compose propose readiness with optional execution readiness checks.
|
|
4944
|
+
* execution?: { provider, registry, timelock, subdao, manifestCID, version }
|
|
4945
|
+
*/
|
|
4946
|
+
readinessToPropose: async function readinessToPropose({ provider, governor, proposer, execution = null }) {
|
|
4947
|
+
const gates = await this.ensureProposeGates({ provider, governor, proposer });
|
|
4948
|
+
let executionReady = null;
|
|
4949
|
+
let executionError = null;
|
|
4950
|
+
if (execution && execution.registry && execution.timelock && execution.subdao) {
|
|
4951
|
+
try {
|
|
4952
|
+
const library = require_library();
|
|
4953
|
+
const { to, data } = library.buildUpdateLibraryTx({
|
|
4954
|
+
registry: execution.registry,
|
|
4955
|
+
subdao: execution.subdao,
|
|
4956
|
+
manifestCID: execution.manifestCID || "",
|
|
4957
|
+
version: execution.version || "1.0.0"
|
|
4958
|
+
});
|
|
4959
|
+
const sim = await library.simulateAsTimelock({ provider, registry: execution.registry, to, data, timelock: execution.timelock });
|
|
4960
|
+
executionReady = !!sim.ok;
|
|
4961
|
+
executionError = sim.ok ? null : sim.error?.message || "revert";
|
|
4962
|
+
} catch (err) {
|
|
4963
|
+
executionReady = false;
|
|
4964
|
+
executionError = String(err && err.message || err);
|
|
4965
|
+
}
|
|
4208
4966
|
}
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4967
|
+
return { ...gates, executionReady, executionError };
|
|
4968
|
+
},
|
|
4969
|
+
/**
|
|
4970
|
+
* Unified authority + readiness envelope for agent-native governance UX.
|
|
4971
|
+
*
|
|
4972
|
+
* This is intentionally **read-only**: it returns diagnostics and optional tx payloads/CLI commands,
|
|
4973
|
+
* but never sends transactions.
|
|
4974
|
+
*
|
|
4975
|
+
* Inputs:
|
|
4976
|
+
* - provider (required)
|
|
4977
|
+
* - subdao OR governor (required)
|
|
4978
|
+
* - account (optional, enables per-account readiness checks)
|
|
4979
|
+
* - action: propose | vote | queue | execute | timelock-schedule | update-library
|
|
4980
|
+
* - proposalId (optional, for queue/execute diagnostics)
|
|
4981
|
+
* - libraryRegistry / dao / manifestCID / version (optional, for update-library diagnostics)
|
|
4982
|
+
*/
|
|
4983
|
+
getAuthorityAndReadiness: async function getAuthorityAndReadiness({
|
|
4984
|
+
provider,
|
|
4985
|
+
subdao: subdaoAddress = null,
|
|
4986
|
+
governor: governorAddress = null,
|
|
4987
|
+
account = null,
|
|
4988
|
+
action,
|
|
4989
|
+
proposalId = null,
|
|
4990
|
+
libraryRegistry = null,
|
|
4991
|
+
dao = null,
|
|
4992
|
+
manifestCID = null,
|
|
4993
|
+
version = null
|
|
4994
|
+
} = {}) {
|
|
4995
|
+
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4996
|
+
if (!subdaoAddress && !governorAddress) throw new SageSDKError(CODES.INVALID_ARGS, "subdao or governor required");
|
|
4997
|
+
const actionNorm = String(action || "propose").trim().toLowerCase();
|
|
4998
|
+
const allowed = /* @__PURE__ */ new Set(["propose", "vote", "queue", "execute", "timelock-schedule", "update-library"]);
|
|
4999
|
+
if (!allowed.has(actionNorm)) {
|
|
5000
|
+
throw new SageSDKError(CODES.INVALID_ARGS, `unsupported action: ${String(action || "")}`);
|
|
5001
|
+
}
|
|
5002
|
+
const readiness = [];
|
|
5003
|
+
const push = (item) => readiness.push(item);
|
|
5004
|
+
const normalizeAccount = () => {
|
|
5005
|
+
if (!account) return null;
|
|
5006
|
+
try {
|
|
5007
|
+
return getAddress(account);
|
|
5008
|
+
} catch (err) {
|
|
5009
|
+
throw new SageSDKError(CODES.INVALID_ARGS, "invalid account address", { cause: err });
|
|
5010
|
+
}
|
|
5011
|
+
};
|
|
5012
|
+
const acct = normalizeAccount();
|
|
5013
|
+
const proposal = {
|
|
5014
|
+
proposalThreshold: null,
|
|
5015
|
+
sxxxToken: null,
|
|
5016
|
+
proposerSxxxBalance: null,
|
|
5017
|
+
thresholdMet: null,
|
|
5018
|
+
cooldownRemaining: null,
|
|
5019
|
+
cooldownElapsed: null,
|
|
5020
|
+
proposerVotes: null,
|
|
5021
|
+
votesTimepoint: null,
|
|
5022
|
+
cancelPolicy: {
|
|
5023
|
+
enabled: null,
|
|
5024
|
+
cancelableStates: ["Pending", "Active"],
|
|
5025
|
+
thresholdSource: "proposalThresholdSnapshot",
|
|
5026
|
+
note: "Keep voting power (delegation + multipliers) above proposalThreshold while proposal is active, or it may be canceled by anyone"
|
|
5027
|
+
},
|
|
5028
|
+
cancelability: null
|
|
5029
|
+
};
|
|
5030
|
+
let subdaoAddr = subdaoAddress ? getAddress(subdaoAddress) : null;
|
|
5031
|
+
let governorAddr = governorAddress ? normaliseGovernor(governorAddress) : null;
|
|
5032
|
+
let timelockAddr = null;
|
|
5033
|
+
let opsSafe = null;
|
|
5034
|
+
if (subdaoAddr) {
|
|
4215
5035
|
try {
|
|
4216
|
-
const
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
5036
|
+
const info = await subdao.getSubDAOInfo({ provider, subdao: subdaoAddr });
|
|
5037
|
+
governorAddr = governorAddr || info.governor;
|
|
5038
|
+
timelockAddr = info.timelock || timelockAddr;
|
|
5039
|
+
opsSafe = info.treasury || null;
|
|
5040
|
+
} catch (err) {
|
|
5041
|
+
push({
|
|
5042
|
+
item: "Resolve SubDAO context",
|
|
5043
|
+
met: false,
|
|
5044
|
+
severity: "warn",
|
|
5045
|
+
evidence: { subdao: subdaoAddr, error: String(err?.message || err) }
|
|
5046
|
+
});
|
|
4220
5047
|
}
|
|
4221
5048
|
}
|
|
4222
|
-
|
|
4223
|
-
|
|
5049
|
+
if (!governorAddr) {
|
|
5050
|
+
throw new SageSDKError(CODES.INVALID_ARGS, "could not resolve governor address");
|
|
5051
|
+
}
|
|
5052
|
+
let govInfo = null;
|
|
4224
5053
|
try {
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
const [addr] = AbiCoder.defaultAbiCoder().decode(["address"], r4);
|
|
4228
|
-
sxxx = getAddress(addr);
|
|
5054
|
+
govInfo = await getGovernorInfo({ provider, governor: governorAddr });
|
|
5055
|
+
timelockAddr = timelockAddr || govInfo.timelock || null;
|
|
4229
5056
|
} catch (_) {
|
|
4230
5057
|
}
|
|
4231
|
-
|
|
5058
|
+
let governanceProfile = { kind: null, proposalAccess: null, executionAccess: null, profileInitialized: false };
|
|
5059
|
+
if (subdaoAddr) {
|
|
4232
5060
|
try {
|
|
4233
|
-
const
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
5061
|
+
const prof = await subdao.getGovernanceProfile({ provider, subdao: subdaoAddr });
|
|
5062
|
+
governanceProfile = {
|
|
5063
|
+
kind: prof.kind,
|
|
5064
|
+
proposalAccess: prof.proposalAccess,
|
|
5065
|
+
executionAccess: prof.executionAccess,
|
|
5066
|
+
profileInitialized: !!prof.profileInitialized
|
|
5067
|
+
};
|
|
4237
5068
|
} catch (_) {
|
|
4238
5069
|
}
|
|
4239
5070
|
}
|
|
4240
|
-
let
|
|
5071
|
+
let votesChain = null;
|
|
4241
5072
|
try {
|
|
4242
|
-
|
|
4243
|
-
const votes = await getVotesLatestMinusOne({ provider, token: votingToken, account: user });
|
|
4244
|
-
const th = threshold != null ? BigInt(threshold) : 0n;
|
|
4245
|
-
votesOk = votes >= th;
|
|
5073
|
+
votesChain = await resolveVotesTokenChain({ provider, subdao: subdaoAddr || void 0, governor: governorAddr });
|
|
4246
5074
|
} catch (_) {
|
|
5075
|
+
votesChain = null;
|
|
5076
|
+
}
|
|
5077
|
+
if (proposalId != null) {
|
|
5078
|
+
try {
|
|
5079
|
+
proposal.cancelability = await getProposalCancelability({ provider, governor: governorAddr, proposalId });
|
|
5080
|
+
} catch (_) {
|
|
5081
|
+
proposal.cancelability = null;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
let timelockInfo = {
|
|
5085
|
+
proposerRole: null,
|
|
5086
|
+
executorRole: null,
|
|
5087
|
+
governorIsProposer: null,
|
|
5088
|
+
governorIsExecutor: null,
|
|
5089
|
+
governorCanExecute: null,
|
|
5090
|
+
anyoneCanExecute: null,
|
|
5091
|
+
accountIsProposer: null,
|
|
5092
|
+
accountIsExecutor: null
|
|
5093
|
+
};
|
|
5094
|
+
if (timelockAddr) {
|
|
5095
|
+
try {
|
|
5096
|
+
const tl = new Contract(timelockAddr, ABI.Timelock, provider);
|
|
5097
|
+
const [PR, ER] = await Promise.all([
|
|
5098
|
+
tl.PROPOSER_ROLE().catch(() => null),
|
|
5099
|
+
tl.EXECUTOR_ROLE().catch(() => null)
|
|
5100
|
+
]);
|
|
5101
|
+
timelockInfo.proposerRole = PR;
|
|
5102
|
+
timelockInfo.executorRole = ER;
|
|
5103
|
+
if (PR && governorAddr) {
|
|
5104
|
+
timelockInfo.governorIsProposer = await tl.hasRole(PR, governorAddr).catch(() => null);
|
|
5105
|
+
}
|
|
5106
|
+
if (ER && governorAddr) {
|
|
5107
|
+
timelockInfo.governorIsExecutor = await tl.hasRole(ER, governorAddr).catch(() => null);
|
|
5108
|
+
}
|
|
5109
|
+
if (ER) {
|
|
5110
|
+
timelockInfo.anyoneCanExecute = await tl.hasRole(ER, ZeroAddress).catch(() => null);
|
|
5111
|
+
}
|
|
5112
|
+
timelockInfo.governorCanExecute = timelockInfo.anyoneCanExecute === true || timelockInfo.governorIsExecutor === true;
|
|
5113
|
+
if (acct && PR) {
|
|
5114
|
+
timelockInfo.accountIsProposer = await tl.hasRole(PR, acct).catch(() => null);
|
|
5115
|
+
}
|
|
5116
|
+
if (acct && ER) {
|
|
5117
|
+
timelockInfo.accountIsExecutor = await tl.hasRole(ER, acct).catch(() => null);
|
|
5118
|
+
}
|
|
5119
|
+
} catch (_) {
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
const governorCanQueue = timelockInfo.governorIsProposer === true;
|
|
5123
|
+
const governorCanExecute = timelockInfo.governorCanExecute === true;
|
|
5124
|
+
const authority = { route: "unknown", rationale: "insufficient context", requiredActor: "unknown" };
|
|
5125
|
+
const requireCouncil = governanceProfile.proposalAccess === "COUNCIL_ONLY";
|
|
5126
|
+
const requireTokenThreshold = governanceProfile.proposalAccess === "COMMUNITY_THRESHOLD";
|
|
5127
|
+
const execIsAnyone = governanceProfile.executionAccess === "ANYONE";
|
|
5128
|
+
const execIsCouncilOnly = governanceProfile.executionAccess === "COUNCIL_ONLY";
|
|
5129
|
+
const safeOrCouncil = opsSafe ? `opsSafe ${opsSafe}` : "council/safe";
|
|
5130
|
+
if (actionNorm === "timelock-schedule") {
|
|
5131
|
+
authority.route = "timelock-schedule";
|
|
5132
|
+
authority.rationale = "Direct timelock scheduling/execution path (operator/council governance)";
|
|
5133
|
+
authority.requiredActor = `Timelock PROPOSER_ROLE holder (often ${safeOrCouncil})`;
|
|
5134
|
+
} else if (actionNorm === "update-library") {
|
|
5135
|
+
if (governorCanQueue && governanceProfile.kind === "TOKEN") {
|
|
5136
|
+
authority.route = "governor-propose";
|
|
5137
|
+
authority.rationale = "LibraryRegistry updates are executed by the DAO timelock; token DAOs route via Governor \u2192 Timelock";
|
|
5138
|
+
authority.requiredActor = requireCouncil ? `COUNCIL_ROLE (${safeOrCouncil})` : "Proposer meeting proposalThreshold";
|
|
5139
|
+
} else {
|
|
5140
|
+
authority.route = "timelock-schedule";
|
|
5141
|
+
authority.rationale = "LibraryRegistry updates are executed by the DAO timelock; operator/council DAOs schedule directly on Timelock";
|
|
5142
|
+
authority.requiredActor = `Timelock PROPOSER_ROLE holder (often ${safeOrCouncil})`;
|
|
5143
|
+
}
|
|
5144
|
+
} else if (actionNorm === "propose") {
|
|
5145
|
+
if (!governorCanQueue && timelockAddr) {
|
|
5146
|
+
authority.route = "timelock-schedule";
|
|
5147
|
+
authority.rationale = "Governor is not a Timelock proposer; governance changes must be scheduled directly on Timelock (operator/council mode)";
|
|
5148
|
+
authority.requiredActor = `Timelock PROPOSER_ROLE holder (often ${safeOrCouncil})`;
|
|
5149
|
+
} else {
|
|
5150
|
+
authority.route = "governor-propose";
|
|
5151
|
+
authority.rationale = "Standard token-governance proposal flow (Governor \u2192 Timelock)";
|
|
5152
|
+
authority.requiredActor = requireCouncil ? `COUNCIL_ROLE (${safeOrCouncil})` : requireTokenThreshold ? "Proposer meeting proposalThreshold" : "Proposer meeting proposalThreshold";
|
|
5153
|
+
}
|
|
5154
|
+
} else if (actionNorm === "vote") {
|
|
5155
|
+
if (governanceProfile.kind === "OPERATOR") {
|
|
5156
|
+
authority.route = "not-applicable";
|
|
5157
|
+
authority.rationale = "Operator governance: token voting is not required for changes";
|
|
5158
|
+
authority.requiredActor = `Operator/council (${safeOrCouncil})`;
|
|
5159
|
+
} else {
|
|
5160
|
+
authority.route = "governor-vote";
|
|
5161
|
+
authority.rationale = "Token governance voting on Governor proposals";
|
|
5162
|
+
authority.requiredActor = "Voter with delegated voting power";
|
|
5163
|
+
}
|
|
5164
|
+
} else if (actionNorm === "queue") {
|
|
5165
|
+
if (governorCanQueue) {
|
|
5166
|
+
authority.route = "governor-queue";
|
|
5167
|
+
authority.rationale = "Queue via GovernorTimelockControl (Governor schedules on Timelock)";
|
|
5168
|
+
authority.requiredActor = "Any account (Governor performs the Timelock schedule as proposer)";
|
|
5169
|
+
} else {
|
|
5170
|
+
authority.route = "timelock-schedule";
|
|
5171
|
+
authority.rationale = "Queue by scheduling directly on Timelock (operator/council mode)";
|
|
5172
|
+
authority.requiredActor = `Timelock PROPOSER_ROLE holder (often ${safeOrCouncil})`;
|
|
5173
|
+
}
|
|
5174
|
+
} else if (actionNorm === "execute") {
|
|
5175
|
+
if (governorCanQueue && governorCanExecute) {
|
|
5176
|
+
authority.route = "governor-execute";
|
|
5177
|
+
authority.rationale = "Execute via GovernorTimelockControl (Timelock.execute called by Governor)";
|
|
5178
|
+
authority.requiredActor = "Any account (Governor performs the Timelock execute)";
|
|
5179
|
+
} else {
|
|
5180
|
+
authority.route = "timelock-execute";
|
|
5181
|
+
authority.rationale = "Execute by calling Timelock.execute directly (restricted executor or operator/council mode)";
|
|
5182
|
+
authority.requiredActor = execIsCouncilOnly ? `Timelock EXECUTOR_ROLE holder (${safeOrCouncil})` : `Timelock EXECUTOR_ROLE holder (often ${safeOrCouncil})`;
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
if (!acct) {
|
|
5186
|
+
push({
|
|
5187
|
+
item: "Account provided for readiness checks",
|
|
5188
|
+
met: false,
|
|
5189
|
+
severity: "info",
|
|
5190
|
+
evidence: { note: "Pass account to enable per-user readiness checks" }
|
|
5191
|
+
});
|
|
5192
|
+
} else {
|
|
5193
|
+
if ((actionNorm === "propose" || actionNorm === "vote") && votesChain && votesChain.votingToken) {
|
|
5194
|
+
try {
|
|
5195
|
+
const res = await getVotesLatestMinusOneWithTimepoint({ provider, token: votesChain.votingToken, account: acct });
|
|
5196
|
+
proposal.proposerVotes = res.votes;
|
|
5197
|
+
proposal.votesTimepoint = res.timepoint;
|
|
5198
|
+
} catch (_) {
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
if (actionNorm === "propose" || actionNorm === "vote") {
|
|
5202
|
+
let voting = null;
|
|
5203
|
+
try {
|
|
5204
|
+
voting = await getVotingStatus({ provider, subdao: subdaoAddr || void 0, governor: governorAddr, account: acct });
|
|
5205
|
+
} catch (_) {
|
|
5206
|
+
voting = null;
|
|
5207
|
+
}
|
|
5208
|
+
if (!voting || !voting.baseToken) {
|
|
5209
|
+
push({ item: "Resolve voting token wiring", met: false, severity: "warn" });
|
|
5210
|
+
} else {
|
|
5211
|
+
const threshold = voting.threshold != null ? BigInt(voting.threshold) : null;
|
|
5212
|
+
const votingPower = voting.votingPower != null ? BigInt(voting.votingPower) : null;
|
|
5213
|
+
let baseBalance = null;
|
|
5214
|
+
let baseDelegate = null;
|
|
5215
|
+
try {
|
|
5216
|
+
const base = new Contract(voting.baseToken, ABI.ERC20Votes, provider);
|
|
5217
|
+
const [bal, del] = await Promise.all([
|
|
5218
|
+
base.balanceOf(acct).catch(() => null),
|
|
5219
|
+
base.delegates(acct).catch(() => null)
|
|
5220
|
+
]);
|
|
5221
|
+
if (bal != null) baseBalance = BigInt(bal.toString());
|
|
5222
|
+
if (del && /^0x[0-9a-fA-F]{40}$/.test(String(del))) baseDelegate = getAddress(del);
|
|
5223
|
+
} catch (_) {
|
|
5224
|
+
}
|
|
5225
|
+
const hasVotes = votingPower != null ? votingPower > 0n : null;
|
|
5226
|
+
push({
|
|
5227
|
+
item: "Voting power is non-zero (delegated)",
|
|
5228
|
+
met: hasVotes,
|
|
5229
|
+
severity: hasVotes === false ? "error" : "info",
|
|
5230
|
+
evidence: {
|
|
5231
|
+
votingPower: votingPower?.toString?.() ?? null,
|
|
5232
|
+
votingToken: voting.votingToken,
|
|
5233
|
+
baseToken: voting.baseToken,
|
|
5234
|
+
note: threshold != null ? `proposalThreshold=${threshold.toString()}` : void 0
|
|
5235
|
+
}
|
|
5236
|
+
});
|
|
5237
|
+
const noDelegateSet = !baseDelegate || baseDelegate === ZeroAddress;
|
|
5238
|
+
if ((actionNorm === "propose" || actionNorm === "vote") && baseBalance != null && baseBalance > 0n && (votingPower === 0n || votingPower === null) && noDelegateSet) {
|
|
5239
|
+
try {
|
|
5240
|
+
const payload = buildDelegateTx({ token: voting.baseToken, delegatee: acct });
|
|
5241
|
+
push({
|
|
5242
|
+
item: "Delegation set (ERC20Votes)",
|
|
5243
|
+
met: false,
|
|
5244
|
+
severity: "error",
|
|
5245
|
+
evidence: { baseToken: voting.baseToken, delegate: baseDelegate || ZeroAddress, baseBalance: baseBalance.toString() },
|
|
5246
|
+
fix: { kind: "tx", commandOrCalldata: payload }
|
|
5247
|
+
});
|
|
5248
|
+
} catch (_) {
|
|
5249
|
+
push({
|
|
5250
|
+
item: "Delegation set (ERC20Votes)",
|
|
5251
|
+
met: false,
|
|
5252
|
+
severity: "error",
|
|
5253
|
+
evidence: { baseToken: voting.baseToken, delegate: baseDelegate || ZeroAddress, baseBalance: baseBalance?.toString?.() ?? null }
|
|
5254
|
+
});
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
if (actionNorm === "propose" && authority.route === "governor-propose") {
|
|
5260
|
+
let thresholdStatus = null;
|
|
5261
|
+
try {
|
|
5262
|
+
thresholdStatus = await getProposalThresholdStatus({ provider, governor: governorAddr, proposer: acct });
|
|
5263
|
+
} catch (_) {
|
|
5264
|
+
}
|
|
5265
|
+
if (thresholdStatus) {
|
|
5266
|
+
const {
|
|
5267
|
+
proposalThreshold,
|
|
5268
|
+
proposerVotes,
|
|
5269
|
+
votesTimepoint,
|
|
5270
|
+
sxxxBalance,
|
|
5271
|
+
thresholdMet,
|
|
5272
|
+
cooldownRemaining,
|
|
5273
|
+
cooldownElapsed,
|
|
5274
|
+
sxxxToken
|
|
5275
|
+
} = thresholdStatus;
|
|
5276
|
+
proposal.proposalThreshold = proposalThreshold;
|
|
5277
|
+
proposal.sxxxToken = sxxxToken;
|
|
5278
|
+
proposal.proposerSxxxBalance = sxxxBalance;
|
|
5279
|
+
proposal.proposerVotes = proposerVotes;
|
|
5280
|
+
proposal.votesTimepoint = votesTimepoint;
|
|
5281
|
+
proposal.thresholdMet = thresholdMet;
|
|
5282
|
+
proposal.cooldownRemaining = cooldownRemaining;
|
|
5283
|
+
proposal.cooldownElapsed = cooldownElapsed;
|
|
5284
|
+
proposal.cancelPolicy.enabled = proposalThreshold != null ? proposalThreshold > 0n : null;
|
|
5285
|
+
if (proposalThreshold != null) {
|
|
5286
|
+
push({
|
|
5287
|
+
item: "Voting power meets proposalThreshold (clock()-1)",
|
|
5288
|
+
met: thresholdMet,
|
|
5289
|
+
severity: thresholdMet === false ? "error" : "info",
|
|
5290
|
+
evidence: {
|
|
5291
|
+
proposerVotes: proposerVotes?.toString?.() ?? null,
|
|
5292
|
+
votesTimepoint: votesTimepoint?.toString?.() ?? null,
|
|
5293
|
+
proposalThreshold: proposalThreshold.toString(),
|
|
5294
|
+
sxxxToken,
|
|
5295
|
+
note: "No approve/escrow needed - delegate and keep sufficient voting power"
|
|
5296
|
+
}
|
|
5297
|
+
});
|
|
5298
|
+
}
|
|
5299
|
+
if (cooldownRemaining != null) {
|
|
5300
|
+
push({
|
|
5301
|
+
item: "Proposal cooldown elapsed",
|
|
5302
|
+
met: cooldownElapsed,
|
|
5303
|
+
severity: cooldownElapsed === false ? "warn" : "info",
|
|
5304
|
+
evidence: { cooldownRemainingSeconds: cooldownRemaining.toString() }
|
|
5305
|
+
});
|
|
5306
|
+
}
|
|
5307
|
+
if (proposalThreshold != null && proposalThreshold > 0n) {
|
|
5308
|
+
push({
|
|
5309
|
+
item: "Note: Proposal cancelability",
|
|
5310
|
+
met: true,
|
|
5311
|
+
severity: "info",
|
|
5312
|
+
evidence: {
|
|
5313
|
+
note: "Keep voting power (delegation + multipliers) above threshold while proposal is active, or it may be canceled by anyone",
|
|
5314
|
+
proposalThreshold: proposalThreshold.toString()
|
|
5315
|
+
}
|
|
5316
|
+
});
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
if (actionNorm === "timelock-schedule" || authority.route === "timelock-schedule") {
|
|
5321
|
+
if (!timelockAddr) {
|
|
5322
|
+
push({ item: "Timelock resolved", met: false, severity: "error" });
|
|
5323
|
+
} else if (timelockInfo.proposerRole) {
|
|
5324
|
+
push({
|
|
5325
|
+
item: "Account has Timelock PROPOSER_ROLE",
|
|
5326
|
+
met: timelockInfo.accountIsProposer,
|
|
5327
|
+
severity: timelockInfo.accountIsProposer === false ? "error" : "info",
|
|
5328
|
+
evidence: { timelock: timelockAddr, account: acct }
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5332
|
+
if ((actionNorm === "queue" || actionNorm === "execute") && proposalId != null) {
|
|
5333
|
+
try {
|
|
5334
|
+
const g = new Contract(governorAddr, ABI.Governor, provider);
|
|
5335
|
+
const pid = typeof proposalId === "bigint" ? proposalId : String(proposalId).startsWith("0x") ? BigInt(String(proposalId)) : BigInt(String(proposalId));
|
|
5336
|
+
const st = await g.state(pid).catch(() => null);
|
|
5337
|
+
if (st != null) {
|
|
5338
|
+
push({
|
|
5339
|
+
item: "Proposal state readable",
|
|
5340
|
+
met: true,
|
|
5341
|
+
severity: "info",
|
|
5342
|
+
evidence: { proposalId: pid.toString(), state: Number(st) }
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5345
|
+
} catch (_) {
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
if (actionNorm === "update-library") {
|
|
5349
|
+
const regAddr = libraryRegistry || process.env.LIBRARY_REGISTRY_ADDRESS || process.env.SAGE_LIBRARY_REGISTRY_ADDRESS || null;
|
|
5350
|
+
const daoAddr = dao ? getAddress(dao) : subdaoAddr || null;
|
|
5351
|
+
if (!regAddr) {
|
|
5352
|
+
push({ item: "LibraryRegistry address provided", met: false, severity: "error" });
|
|
5353
|
+
} else if (!daoAddr) {
|
|
5354
|
+
push({ item: "DAO/SubDAO address provided", met: false, severity: "error" });
|
|
5355
|
+
} else if (!timelockAddr) {
|
|
5356
|
+
push({ item: "Timelock resolved for DAO", met: false, severity: "error" });
|
|
5357
|
+
} else {
|
|
5358
|
+
try {
|
|
5359
|
+
const reg = new Contract(getAddress(regAddr), ABI.LibraryRegistry, provider);
|
|
5360
|
+
const daoTl = await reg.daoTimelock(daoAddr).catch(() => null);
|
|
5361
|
+
const daoTimelock = daoTl && /^0x[0-9a-fA-F]{40}$/.test(String(daoTl)) ? getAddress(daoTl) : null;
|
|
5362
|
+
const match = daoTimelock ? daoTimelock.toLowerCase() === timelockAddr.toLowerCase() : null;
|
|
5363
|
+
push({
|
|
5364
|
+
item: "LibraryRegistry daoTimelock matches resolved Timelock",
|
|
5365
|
+
met: match,
|
|
5366
|
+
severity: match === false ? "error" : "info",
|
|
5367
|
+
evidence: { libraryRegistry: getAddress(regAddr), dao: daoAddr, daoTimelock, timelock: timelockAddr }
|
|
5368
|
+
});
|
|
5369
|
+
if (match === true && manifestCID) {
|
|
5370
|
+
const library = require_library();
|
|
5371
|
+
const payload = library.buildUpdateLibraryTx({
|
|
5372
|
+
registry: getAddress(regAddr),
|
|
5373
|
+
subdao: daoAddr,
|
|
5374
|
+
manifestCID: String(manifestCID),
|
|
5375
|
+
version: String(version || "1.0.0")
|
|
5376
|
+
});
|
|
5377
|
+
const sim = await library.simulateAsTimelock({ provider, registry: getAddress(regAddr), to: payload.to, data: payload.data, timelock: timelockAddr });
|
|
5378
|
+
push({
|
|
5379
|
+
item: "Simulate LibraryRegistry.updateLibrary as Timelock",
|
|
5380
|
+
met: !!sim.ok,
|
|
5381
|
+
severity: sim.ok ? "info" : "error",
|
|
5382
|
+
evidence: sim.ok ? { ok: true } : { ok: false, error: sim.error?.message || "revert" }
|
|
5383
|
+
});
|
|
5384
|
+
}
|
|
5385
|
+
} catch (_) {
|
|
5386
|
+
}
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
4247
5389
|
}
|
|
4248
5390
|
return {
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
5391
|
+
governanceProfile,
|
|
5392
|
+
proposal,
|
|
5393
|
+
addresses: {
|
|
5394
|
+
subdao: subdaoAddr,
|
|
5395
|
+
governor: governorAddr,
|
|
5396
|
+
timelock: timelockAddr,
|
|
5397
|
+
opsSafe,
|
|
5398
|
+
votingToken: votesChain ? votesChain.votingToken : null,
|
|
5399
|
+
baseVotingToken: votesChain ? votesChain.baseToken : null
|
|
5400
|
+
},
|
|
5401
|
+
authority,
|
|
5402
|
+
readiness
|
|
4256
5403
|
};
|
|
4257
5404
|
},
|
|
4258
|
-
/**
|
|
4259
|
-
* Compose propose readiness with optional execution readiness checks.
|
|
4260
|
-
* execution?: { provider, registry, timelock, subdao, manifestCID, version }
|
|
4261
|
-
*/
|
|
4262
|
-
readinessToPropose: async function readinessToPropose({ provider, governor, proposer, execution = null }) {
|
|
4263
|
-
const gates = await this.ensureProposeGates({ provider, governor, proposer });
|
|
4264
|
-
let executionReady = null;
|
|
4265
|
-
let executionError = null;
|
|
4266
|
-
if (execution && execution.registry && execution.timelock && execution.subdao) {
|
|
4267
|
-
try {
|
|
4268
|
-
const library = require_library();
|
|
4269
|
-
const { to, data } = library.buildUpdateLibraryTx({
|
|
4270
|
-
registry: execution.registry,
|
|
4271
|
-
subdao: execution.subdao,
|
|
4272
|
-
manifestCID: execution.manifestCID || "",
|
|
4273
|
-
version: execution.version || "1.0.0"
|
|
4274
|
-
});
|
|
4275
|
-
const sim = await library.simulateAsTimelock({ provider, registry: execution.registry, to, data, timelock: execution.timelock });
|
|
4276
|
-
executionReady = !!sim.ok;
|
|
4277
|
-
executionError = sim.ok ? null : sim.error?.message || "revert";
|
|
4278
|
-
} catch (err) {
|
|
4279
|
-
executionReady = false;
|
|
4280
|
-
executionError = String(err && err.message || err);
|
|
4281
|
-
}
|
|
4282
|
-
}
|
|
4283
|
-
return { ...gates, executionReady, executionError };
|
|
4284
|
-
},
|
|
4285
5405
|
/** List active proposals (default states: PENDING, ACTIVE) via subgraph. */
|
|
4286
5406
|
listActiveProposals: async function listActiveProposals({ url, governor, first = 20, skip = 0 }) {
|
|
4287
5407
|
if (!url) throw new SageSDKError(CODES.INVALID_ARGS, "subgraph url required");
|
|
@@ -4307,22 +5427,21 @@ var require_governance = __commonJS({
|
|
|
4307
5427
|
/**
|
|
4308
5428
|
* Detect governance mode and operator status.
|
|
4309
5429
|
* Heuristics mirror CLI detectGovMode.
|
|
4310
|
-
* Returns { operator: boolean, governance: 'Personal'|'Community'|'Unknown', governor, timelock, subdao,
|
|
5430
|
+
* Returns { operator: boolean, governance: 'Personal'|'Community'|'Unknown', governor, timelock, subdao, depositWei }
|
|
4311
5431
|
*/
|
|
4312
|
-
detectMode: async function detectMode({ provider, governor, timelock, subdao }) {
|
|
5432
|
+
detectMode: async function detectMode({ provider, governor, timelock, subdao: subdao2 }) {
|
|
4313
5433
|
const out = {
|
|
4314
5434
|
operator: false,
|
|
4315
5435
|
governance: "Unknown",
|
|
4316
5436
|
governor: governor || null,
|
|
4317
5437
|
timelock: timelock || null,
|
|
4318
|
-
subdao:
|
|
4319
|
-
stakeRequired: null,
|
|
5438
|
+
subdao: subdao2 || null,
|
|
4320
5439
|
depositWei: null
|
|
4321
5440
|
};
|
|
4322
5441
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4323
5442
|
try {
|
|
4324
|
-
if (
|
|
4325
|
-
const sub = new Contract(getAddress(
|
|
5443
|
+
if (subdao2 && (!governor || !timelock)) {
|
|
5444
|
+
const sub = new Contract(getAddress(subdao2), ["function governor() view returns (address)", "function timelock() view returns (address)", "function getGovernanceMode() view returns (uint8)"], provider);
|
|
4326
5445
|
if (!governor) out.governor = await sub.governor();
|
|
4327
5446
|
if (!timelock) out.timelock = await sub.timelock();
|
|
4328
5447
|
try {
|
|
@@ -4344,313 +5463,61 @@ var require_governance = __commonJS({
|
|
|
4344
5463
|
}
|
|
4345
5464
|
try {
|
|
4346
5465
|
if (out.governor) {
|
|
4347
|
-
const
|
|
4348
|
-
const
|
|
4349
|
-
if (
|
|
4350
|
-
const ifaceDep = new Interface(["function bootstrapDepositWei() view returns (uint256)"]);
|
|
4351
|
-
const retDep = await provider.call({ to: getAddress(out.governor), data: ifaceDep.encodeFunctionData("bootstrapDepositWei", []) }).catch(() => null);
|
|
4352
|
-
if (retDep && retDep !== "0x") out.depositWei = BigInt(retDep);
|
|
4353
|
-
}
|
|
4354
|
-
} catch (_) {
|
|
4355
|
-
}
|
|
4356
|
-
return out;
|
|
4357
|
-
}
|
|
4358
|
-
};
|
|
4359
|
-
}
|
|
4360
|
-
});
|
|
4361
|
-
|
|
4362
|
-
// src/governance/intents.js
|
|
4363
|
-
var require_intents = __commonJS({
|
|
4364
|
-
"src/governance/intents.js"(exports2, module2) {
|
|
4365
|
-
var { Interface, getAddress } = __require("ethers");
|
|
4366
|
-
var { SageSDKError, CODES } = require_errors();
|
|
4367
|
-
function normalise(addr, label) {
|
|
4368
|
-
try {
|
|
4369
|
-
return getAddress(addr);
|
|
4370
|
-
} catch {
|
|
4371
|
-
throw new SageSDKError(CODES.INVALID_ARGS, `${label} required/invalid`);
|
|
4372
|
-
}
|
|
4373
|
-
}
|
|
4374
|
-
var GovIface = new Interface([
|
|
4375
|
-
"function updateQuorumNumerator(uint256)",
|
|
4376
|
-
"function setVotingPeriodBlocks(uint32)",
|
|
4377
|
-
"function setDepositOnlyMode(bool)"
|
|
4378
|
-
]);
|
|
4379
|
-
var RegIface = new Interface([
|
|
4380
|
-
"function setForkPolicy(uint8)",
|
|
4381
|
-
"function updatePromptByGovernance(string,string)",
|
|
4382
|
-
"function addPrompt(string,bytes32[],string,string,uint8)"
|
|
4383
|
-
]);
|
|
4384
|
-
var CfgIface = new Interface([
|
|
4385
|
-
"function setPremiumRevSplit(address,uint16,uint16,uint16)",
|
|
4386
|
-
"function setCreatorStakeBps(address,uint256)"
|
|
4387
|
-
]);
|
|
4388
|
-
module2.exports = {
|
|
4389
|
-
// Governor targets
|
|
4390
|
-
buildUpdateQuorumTx: ({ governor, quorumPercent }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("updateQuorumNumerator", [Number(quorumPercent)]), value: 0n }),
|
|
4391
|
-
buildSetVotingPeriodTx: ({ governor, blocks }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("setVotingPeriodBlocks", [Number(blocks)]), value: 0n }),
|
|
4392
|
-
buildSetDepositOnlyTx: ({ governor, enabled }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("setDepositOnlyMode", [!!enabled]), value: 0n }),
|
|
4393
|
-
// Registry targets
|
|
4394
|
-
buildSetForkPolicyTx: ({ registry, policy }) => ({ to: normalise(registry, "registry"), data: RegIface.encodeFunctionData("setForkPolicy", [Number(policy)]), value: 0n }),
|
|
4395
|
-
buildUpdatePromptByGovernanceTx: ({ registry, key, cid }) => ({ to: normalise(registry, "registry"), data: RegIface.encodeFunctionData("updatePromptByGovernance", [String(key), String(cid)]), value: 0n }),
|
|
4396
|
-
buildAddPromptTx: ({ registry, title, tags = [], contentCID, metadataCID = "", category = 0 }) => ({ to: normalise(registry, "registry"), data: RegIface.encodeFunctionData("addPrompt", [String(title), tags, String(contentCID), String(metadataCID), Number(category)]), value: 0n }),
|
|
4397
|
-
// GovernanceConfig targets
|
|
4398
|
-
buildSetPremiumSplitTx: ({ config, subdao, treasuryBps, protocolBps, creatorBps }) => ({ to: normalise(config, "config"), data: CfgIface.encodeFunctionData("setPremiumRevSplit", [normalise(subdao, "subdao"), Number(treasuryBps), Number(protocolBps), Number(creatorBps)]), value: 0n }),
|
|
4399
|
-
buildSetCreatorStakeBpsTx: ({ config, subdao, bps }) => ({ to: normalise(config, "config"), data: CfgIface.encodeFunctionData("setCreatorStakeBps", [normalise(subdao, "subdao"), Number(bps)]), value: 0n })
|
|
4400
|
-
};
|
|
4401
|
-
}
|
|
4402
|
-
});
|
|
4403
|
-
|
|
4404
|
-
// src/utils/logs.js
|
|
4405
|
-
var require_logs = __commonJS({
|
|
4406
|
-
"src/utils/logs.js"(exports2, module2) {
|
|
4407
|
-
async function fetchLogsChunked({ provider, address, topics = [], fromBlock = 0, toBlock = "latest", chunkSize = 5e4 }) {
|
|
4408
|
-
if (!provider) throw new Error("provider required");
|
|
4409
|
-
const latest = toBlock === "latest" ? await provider.getBlockNumber() : Number(toBlock);
|
|
4410
|
-
let start = Number(fromBlock);
|
|
4411
|
-
const logs = [];
|
|
4412
|
-
while (start <= latest) {
|
|
4413
|
-
const end = Math.min(start + chunkSize - 1, latest);
|
|
4414
|
-
const chunk = await provider.getLogs({ address, topics, fromBlock: start, toBlock: end });
|
|
4415
|
-
logs.push(...chunk);
|
|
4416
|
-
start = end + 1;
|
|
4417
|
-
}
|
|
4418
|
-
return logs;
|
|
4419
|
-
}
|
|
4420
|
-
module2.exports = {
|
|
4421
|
-
fetchLogsChunked
|
|
4422
|
-
};
|
|
4423
|
-
}
|
|
4424
|
-
});
|
|
4425
|
-
|
|
4426
|
-
// src/subdao/index.js
|
|
4427
|
-
var require_subdao = __commonJS({
|
|
4428
|
-
"src/subdao/index.js"(exports2, module2) {
|
|
4429
|
-
var { Contract, Interface, getAddress } = __require("ethers");
|
|
4430
|
-
var ABI = require_abi();
|
|
4431
|
-
var { fetchLogsChunked } = require_logs();
|
|
4432
|
-
var { SageSDKError, CODES } = require_errors();
|
|
4433
|
-
function normalise(address, label) {
|
|
4434
|
-
if (!address) throw new SageSDKError(CODES.INVALID_ARGS, `${label} required`);
|
|
4435
|
-
try {
|
|
4436
|
-
return getAddress(address);
|
|
4437
|
-
} catch (err) {
|
|
4438
|
-
throw new SageSDKError(CODES.INVALID_ARGS, `invalid ${label}`, { cause: err });
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
async function discoverSubDAOs({ provider, factoryAddress, fromBlock = 0, toBlock = "latest", limit = 5e3, chunkSize = 5e4 }) {
|
|
4442
|
-
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4443
|
-
const fac = normalise(factoryAddress, "factoryAddress");
|
|
4444
|
-
const iface = new Interface(ABI.Factory);
|
|
4445
|
-
const topic = iface.getEvent("SubDAOGovernanceDeployed").topicHash;
|
|
4446
|
-
const logs = await fetchLogsChunked({ provider, address: fac, topics: [topic], fromBlock, toBlock, chunkSize });
|
|
4447
|
-
const out = [];
|
|
4448
|
-
const slice = limit > 0 ? logs.slice(-limit) : logs;
|
|
4449
|
-
for (const log of slice) {
|
|
4450
|
-
try {
|
|
4451
|
-
const decoded = iface.decodeEventLog("SubDAOGovernanceDeployed", log.data, log.topics);
|
|
4452
|
-
out.push({
|
|
4453
|
-
subdao: getAddress(decoded[0]),
|
|
4454
|
-
governor: getAddress(decoded[1]),
|
|
4455
|
-
timelock: getAddress(decoded[2]),
|
|
4456
|
-
treasury: decoded[3] ? getAddress(decoded[3]) : null,
|
|
4457
|
-
txHash: log.transactionHash,
|
|
4458
|
-
blockNumber: log.blockNumber
|
|
4459
|
-
});
|
|
4460
|
-
} catch (err) {
|
|
4461
|
-
continue;
|
|
4462
|
-
}
|
|
4463
|
-
}
|
|
4464
|
-
return out;
|
|
4465
|
-
}
|
|
4466
|
-
async function getSubDAOInfo({ provider, subdao }) {
|
|
4467
|
-
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4468
|
-
const addr = normalise(subdao, "subdao");
|
|
4469
|
-
const contract = new Contract(addr, ABI.SubDAO, provider);
|
|
4470
|
-
const [governor, timelock, mode, promptRegistry, sageTreasury, stakeToken, minStakeAmount, accessModel, membershipPolicy, profileCID, factoryDeployer] = await Promise.all([
|
|
4471
|
-
contract.governor(),
|
|
4472
|
-
contract.timelock(),
|
|
4473
|
-
contract.getGovernanceMode().catch(() => null),
|
|
4474
|
-
contract.promptRegistry().catch(() => null),
|
|
4475
|
-
contract.sageTreasury().catch(() => null),
|
|
4476
|
-
contract.stakeToken().catch(() => null),
|
|
4477
|
-
contract.minStakeAmount().catch(() => null),
|
|
4478
|
-
contract.accessModel().catch(() => null),
|
|
4479
|
-
contract.membershipPolicy().catch(() => null),
|
|
4480
|
-
contract.profileCID().catch(() => null),
|
|
4481
|
-
contract.factoryDeployer().catch(() => null)
|
|
4482
|
-
]);
|
|
4483
|
-
return {
|
|
4484
|
-
address: addr,
|
|
4485
|
-
governor: getAddress(governor),
|
|
4486
|
-
timelock: getAddress(timelock),
|
|
4487
|
-
governanceMode: mode,
|
|
4488
|
-
promptRegistry: promptRegistry ? getAddress(promptRegistry) : null,
|
|
4489
|
-
sageTreasury: sageTreasury ? getAddress(sageTreasury) : null,
|
|
4490
|
-
stakeToken: stakeToken && stakeToken !== "0x0000000000000000000000000000000000000000" ? getAddress(stakeToken) : null,
|
|
4491
|
-
minStakeAmount: minStakeAmount != null ? BigInt(minStakeAmount.toString()) : null,
|
|
4492
|
-
accessModel: accessModel != null ? Number(accessModel) : null,
|
|
4493
|
-
membershipPolicy: membershipPolicy != null ? Number(membershipPolicy) : null,
|
|
4494
|
-
profileCID: profileCID || null,
|
|
4495
|
-
factoryDeployer: factoryDeployer && factoryDeployer !== "0x0000000000000000000000000000000000000000" ? getAddress(factoryDeployer) : null
|
|
4496
|
-
};
|
|
4497
|
-
}
|
|
4498
|
-
async function getSubDAOUserStats({ provider, subdao, account }) {
|
|
4499
|
-
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
4500
|
-
const addr = normalise(subdao, "subdao");
|
|
4501
|
-
const user = normalise(account, "account");
|
|
4502
|
-
const contract = new Contract(addr, ABI.SubDAO, provider);
|
|
4503
|
-
const [staked, userForkCount] = await Promise.all([
|
|
4504
|
-
contract.stakedAmount(user).catch(() => 0n),
|
|
4505
|
-
contract.userForkCount(user).catch(() => 0n)
|
|
4506
|
-
]);
|
|
4507
|
-
return {
|
|
4508
|
-
stakedAmount: BigInt(staked.toString()),
|
|
4509
|
-
userForkCount: Number(userForkCount)
|
|
4510
|
-
};
|
|
4511
|
-
}
|
|
4512
|
-
var SubDAOInterface = new Interface([
|
|
4513
|
-
"function stake(uint256)",
|
|
4514
|
-
"function unstake(uint256)",
|
|
4515
|
-
// Optional on newer SubDAO implementations; provided for tx building only.
|
|
4516
|
-
"function setProfileCid(string)"
|
|
4517
|
-
]);
|
|
4518
|
-
function buildStakeTx({ subdao, amount }) {
|
|
4519
|
-
const addr = normalise(subdao, "subdao");
|
|
4520
|
-
const data = SubDAOInterface.encodeFunctionData("stake", [BigInt(amount)]);
|
|
4521
|
-
return { to: addr, data, value: 0n };
|
|
4522
|
-
}
|
|
4523
|
-
function buildUnstakeTx({ subdao, amount }) {
|
|
4524
|
-
const addr = normalise(subdao, "subdao");
|
|
4525
|
-
const data = SubDAOInterface.encodeFunctionData("unstake", [BigInt(amount)]);
|
|
4526
|
-
return { to: addr, data, value: 0n };
|
|
4527
|
-
}
|
|
4528
|
-
function buildSetProfileCidTx({ subdao, profileCid }) {
|
|
4529
|
-
const addr = normalise(subdao, "subdao");
|
|
4530
|
-
if (!profileCid || typeof profileCid !== "string" || !profileCid.trim()) {
|
|
4531
|
-
throw new SageSDKError(CODES.INVALID_ARGS, "profileCid (CID string) required");
|
|
4532
|
-
}
|
|
4533
|
-
const data = SubDAOInterface.encodeFunctionData("setProfileCid", [String(profileCid)]);
|
|
4534
|
-
return { to: addr, data, value: 0n };
|
|
4535
|
-
}
|
|
4536
|
-
module2.exports = {
|
|
4537
|
-
discoverSubDAOs,
|
|
4538
|
-
getSubDAOInfo,
|
|
4539
|
-
getSubDAOUserStats,
|
|
4540
|
-
buildStakeTx,
|
|
4541
|
-
buildUnstakeTx,
|
|
4542
|
-
buildSetProfileCidTx
|
|
4543
|
-
};
|
|
4544
|
-
async function ensureSxxxBurnAllowance({ signer, sxxx, owner, spender, amount }) {
|
|
4545
|
-
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
4546
|
-
if (!sxxx) throw new SageSDKError(CODES.INVALID_ARGS, "sxxx address required");
|
|
4547
|
-
if (!spender) throw new SageSDKError(CODES.INVALID_ARGS, "spender (factory) required");
|
|
4548
|
-
const sxxxAddr = getAddress(sxxx);
|
|
4549
|
-
const spenderAddr = getAddress(spender);
|
|
4550
|
-
const erc = new Contract(sxxxAddr, ABI.SXXX, signer);
|
|
4551
|
-
const ownerAddr = owner ? getAddress(owner) : await signer.getAddress();
|
|
4552
|
-
const need = BigInt(amount || 0n);
|
|
4553
|
-
if (need === 0n) return { approved: false, tx: null };
|
|
4554
|
-
const current = await erc.allowance(ownerAddr, spenderAddr);
|
|
4555
|
-
const cur = BigInt(current.toString());
|
|
4556
|
-
if (cur >= need) return { approved: false, tx: null };
|
|
4557
|
-
const tx = await erc.approve(spenderAddr, need);
|
|
4558
|
-
const rcpt = await tx.wait();
|
|
4559
|
-
return { approved: true, tx, receipt: rcpt };
|
|
4560
|
-
}
|
|
4561
|
-
async function createSubDAO({ signer, factoryAddress, name, description, accessModel, minStakeAmount, burnAmount, sxxx }) {
|
|
4562
|
-
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
4563
|
-
if (!factoryAddress) throw new SageSDKError(CODES.INVALID_ARGS, "factoryAddress required");
|
|
4564
|
-
const provider = signer.provider;
|
|
4565
|
-
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer.provider required");
|
|
4566
|
-
if (sxxx && BigInt(burnAmount || 0n) > 0n) {
|
|
4567
|
-
await ensureSxxxBurnAllowance({ signer, sxxx, spender: factoryAddress, amount: BigInt(burnAmount) });
|
|
4568
|
-
}
|
|
4569
|
-
const fac = new Contract(getAddress(factoryAddress), ABI.FactoryWrite, signer);
|
|
4570
|
-
const args = [
|
|
4571
|
-
String(name || ""),
|
|
4572
|
-
String(description || ""),
|
|
4573
|
-
Number(accessModel ?? 0),
|
|
4574
|
-
BigInt(minStakeAmount ?? 0n),
|
|
4575
|
-
BigInt(burnAmount ?? 0n)
|
|
4576
|
-
];
|
|
4577
|
-
let tx;
|
|
4578
|
-
try {
|
|
4579
|
-
tx = await fac.createSubDAO(...args);
|
|
4580
|
-
} catch (err) {
|
|
4581
|
-
tx = await fac.createSubDAO(...args, { gasLimit: 8e6 });
|
|
4582
|
-
}
|
|
4583
|
-
const receipt = await tx.wait();
|
|
4584
|
-
const iface = new Interface(ABI.Factory);
|
|
4585
|
-
let subdao = null, governor = null, timelock = null, treasury = null, registry = null;
|
|
4586
|
-
for (const log of receipt.logs || []) {
|
|
4587
|
-
try {
|
|
4588
|
-
const parsed = iface.parseLog(log);
|
|
4589
|
-
if (parsed && parsed.name === "SubDAOGovernanceDeployed") {
|
|
4590
|
-
subdao = getAddress(parsed.args.subDAO || parsed.args[0]);
|
|
4591
|
-
governor = getAddress(parsed.args.governor || parsed.args[1]);
|
|
4592
|
-
timelock = getAddress(parsed.args.timelock || parsed.args[2]);
|
|
4593
|
-
treasury = getAddress(parsed.args.treasury || parsed.args[3]);
|
|
4594
|
-
} else if (parsed && parsed.name === "SubDAOCreated") {
|
|
4595
|
-
const maybe = parsed.args.registry || parsed.args[2];
|
|
4596
|
-
if (maybe) registry = getAddress(maybe);
|
|
4597
|
-
if (!subdao) subdao = getAddress(parsed.args.subDAO || parsed.args[0]);
|
|
5466
|
+
const ifaceDep = new Interface(["function bootstrapDepositWei() view returns (uint256)"]);
|
|
5467
|
+
const retDep = await provider.call({ to: getAddress(out.governor), data: ifaceDep.encodeFunctionData("bootstrapDepositWei", []) }).catch(() => null);
|
|
5468
|
+
if (retDep && retDep !== "0x") out.depositWei = BigInt(retDep);
|
|
4598
5469
|
}
|
|
4599
5470
|
} catch (_) {
|
|
4600
5471
|
}
|
|
5472
|
+
return out;
|
|
4601
5473
|
}
|
|
5474
|
+
};
|
|
5475
|
+
}
|
|
5476
|
+
});
|
|
5477
|
+
|
|
5478
|
+
// src/governance/intents.js
|
|
5479
|
+
var require_intents = __commonJS({
|
|
5480
|
+
"src/governance/intents.js"(exports2, module2) {
|
|
5481
|
+
var { Interface, getAddress } = __require("ethers");
|
|
5482
|
+
var { SageSDKError, CODES } = require_errors();
|
|
5483
|
+
function normalise(addr, label) {
|
|
4602
5484
|
try {
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
if (reg && /^0x/.test(String(reg))) registry = getAddress(reg);
|
|
4607
|
-
}
|
|
4608
|
-
} catch (_) {
|
|
4609
|
-
}
|
|
4610
|
-
return { tx, receipt, subdao, governor, timelock, treasury, registry };
|
|
4611
|
-
}
|
|
4612
|
-
async function makeOperator({ signer, subdao, operator, grantAdmin = false }) {
|
|
4613
|
-
if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
|
|
4614
|
-
const provider = signer.provider;
|
|
4615
|
-
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "signer.provider required");
|
|
4616
|
-
const addr = normalise(subdao, "subdao");
|
|
4617
|
-
const sub = new Contract(addr, ABI.SubDAO, provider);
|
|
4618
|
-
const governor = getAddress(await sub.governor());
|
|
4619
|
-
const timelock = getAddress(await sub.timelock());
|
|
4620
|
-
const TL_ABI = [
|
|
4621
|
-
"function PROPOSER_ROLE() view returns (bytes32)",
|
|
4622
|
-
"function EXECUTOR_ROLE() view returns (bytes32)",
|
|
4623
|
-
"function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
|
|
4624
|
-
"function grantRole(bytes32,address)",
|
|
4625
|
-
"function revokeRole(bytes32,address)"
|
|
4626
|
-
];
|
|
4627
|
-
const tl = new Contract(timelock, TL_ABI, signer);
|
|
4628
|
-
const [PR, ER, AR] = await Promise.all([
|
|
4629
|
-
tl.PROPOSER_ROLE(),
|
|
4630
|
-
tl.EXECUTOR_ROLE(),
|
|
4631
|
-
tl.DEFAULT_ADMIN_ROLE().catch(() => null)
|
|
4632
|
-
]);
|
|
4633
|
-
const ops = [];
|
|
4634
|
-
ops.push(await tl.grantRole(PR, getAddress(operator)));
|
|
4635
|
-
ops.push(await tl.grantRole(ER, getAddress(operator)));
|
|
4636
|
-
ops.push(await tl.revokeRole(PR, governor));
|
|
4637
|
-
if (grantAdmin && AR) ops.push(await tl.grantRole(AR, getAddress(operator)));
|
|
4638
|
-
const receipts = [];
|
|
4639
|
-
for (const tx of ops) {
|
|
4640
|
-
receipts.push(await tx.wait());
|
|
5485
|
+
return getAddress(addr);
|
|
5486
|
+
} catch {
|
|
5487
|
+
throw new SageSDKError(CODES.INVALID_ARGS, `${label} required/invalid`);
|
|
4641
5488
|
}
|
|
4642
|
-
return { governor, timelock, operator: getAddress(operator), txs: ops, receipts };
|
|
4643
5489
|
}
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
5490
|
+
var GovIface = new Interface([
|
|
5491
|
+
"function setQuorumVotes(uint256)",
|
|
5492
|
+
"function setVotingPeriodBlocks(uint32)",
|
|
5493
|
+
"function setDepositOnlyMode(bool)",
|
|
5494
|
+
// Legacy (deprecated)
|
|
5495
|
+
"function updateQuorumNumerator(uint256)"
|
|
5496
|
+
]);
|
|
5497
|
+
var RegIface = new Interface([
|
|
5498
|
+
"function setForkPolicy(uint8)",
|
|
5499
|
+
"function updatePromptByGovernance(string,string)",
|
|
5500
|
+
"function addPrompt(string,bytes32[],string,string,uint8)"
|
|
5501
|
+
]);
|
|
5502
|
+
var CfgIface = new Interface([
|
|
5503
|
+
"function setPremiumRevSplit(address,uint16,uint16,uint16)",
|
|
5504
|
+
"function setCreatorStakeBps(address,uint256)"
|
|
5505
|
+
]);
|
|
5506
|
+
module2.exports = {
|
|
5507
|
+
// Governor targets
|
|
5508
|
+
buildSetQuorumVotesTx: ({ governor, quorumVotes }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("setQuorumVotes", [BigInt(quorumVotes)]), value: 0n }),
|
|
5509
|
+
buildSetVotingPeriodTx: ({ governor, blocks }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("setVotingPeriodBlocks", [Number(blocks)]), value: 0n }),
|
|
5510
|
+
buildSetDepositOnlyTx: ({ governor, enabled }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("setDepositOnlyMode", [!!enabled]), value: 0n }),
|
|
5511
|
+
// Legacy (deprecated) - use buildSetQuorumVotesTx instead
|
|
5512
|
+
buildUpdateQuorumTx: ({ governor, quorumPercent }) => ({ to: normalise(governor, "governor"), data: GovIface.encodeFunctionData("updateQuorumNumerator", [Number(quorumPercent)]), value: 0n }),
|
|
5513
|
+
// Registry targets
|
|
5514
|
+
buildSetForkPolicyTx: ({ registry, policy }) => ({ to: normalise(registry, "registry"), data: RegIface.encodeFunctionData("setForkPolicy", [Number(policy)]), value: 0n }),
|
|
5515
|
+
buildUpdatePromptByGovernanceTx: ({ registry, key, cid }) => ({ to: normalise(registry, "registry"), data: RegIface.encodeFunctionData("updatePromptByGovernance", [String(key), String(cid)]), value: 0n }),
|
|
5516
|
+
buildAddPromptTx: ({ registry, title, tags = [], contentCID, metadataCID = "", category = 0 }) => ({ to: normalise(registry, "registry"), data: RegIface.encodeFunctionData("addPrompt", [String(title), tags, String(contentCID), String(metadataCID), Number(category)]), value: 0n }),
|
|
5517
|
+
// GovernanceConfig targets
|
|
5518
|
+
buildSetPremiumSplitTx: ({ config, subdao, treasuryBps, protocolBps, creatorBps }) => ({ to: normalise(config, "config"), data: CfgIface.encodeFunctionData("setPremiumRevSplit", [normalise(subdao, "subdao"), Number(treasuryBps), Number(protocolBps), Number(creatorBps)]), value: 0n }),
|
|
5519
|
+
buildSetCreatorStakeBpsTx: ({ config, subdao, bps }) => ({ to: normalise(config, "config"), data: CfgIface.encodeFunctionData("setCreatorStakeBps", [normalise(subdao, "subdao"), Number(bps)]), value: 0n })
|
|
5520
|
+
};
|
|
4654
5521
|
}
|
|
4655
5522
|
});
|
|
4656
5523
|
|
|
@@ -4696,7 +5563,6 @@ var require_templates = __commonJS({
|
|
|
4696
5563
|
signer,
|
|
4697
5564
|
subdao: subdaoAddr,
|
|
4698
5565
|
operator,
|
|
4699
|
-
stakeAmount,
|
|
4700
5566
|
cooldownSeconds,
|
|
4701
5567
|
depositOnly = true
|
|
4702
5568
|
}) {
|
|
@@ -4714,10 +5580,6 @@ var require_templates = __commonJS({
|
|
|
4714
5580
|
const operatorHasExecutor = await tl.hasRole(executorRole, operatorAddress).catch(() => false);
|
|
4715
5581
|
if (!operatorHasExecutor) actions.push(() => tl.grantRole(executorRole, operatorAddress));
|
|
4716
5582
|
const governorContract = new Contract(governor, abi.Governor, signer);
|
|
4717
|
-
if (stakeAmount != null) {
|
|
4718
|
-
const value = BigInt(stakeAmount);
|
|
4719
|
-
actions.push(() => governorContract.setProposalStakeRequired(value));
|
|
4720
|
-
}
|
|
4721
5583
|
if (cooldownSeconds != null) {
|
|
4722
5584
|
const value = BigInt(cooldownSeconds);
|
|
4723
5585
|
actions.push(() => governorContract.setProposalCooldown(value));
|
|
@@ -4732,7 +5594,6 @@ var require_templates = __commonJS({
|
|
|
4732
5594
|
provider,
|
|
4733
5595
|
signer,
|
|
4734
5596
|
subdao: subdaoAddr,
|
|
4735
|
-
stakeAmount,
|
|
4736
5597
|
cooldownSeconds,
|
|
4737
5598
|
anyoneExec = true,
|
|
4738
5599
|
depositOnly = false
|
|
@@ -4752,10 +5613,6 @@ var require_templates = __commonJS({
|
|
|
4752
5613
|
if (!hasZeroExec) actions.push(() => tl.grantRole(executorRole, ZeroAddress));
|
|
4753
5614
|
}
|
|
4754
5615
|
const governorContract = new Contract(governor, abi.Governor, signer);
|
|
4755
|
-
if (stakeAmount != null) {
|
|
4756
|
-
const value = BigInt(stakeAmount);
|
|
4757
|
-
actions.push(() => governorContract.setProposalStakeRequired(value));
|
|
4758
|
-
}
|
|
4759
5616
|
if (cooldownSeconds != null) {
|
|
4760
5617
|
const value = BigInt(cooldownSeconds);
|
|
4761
5618
|
actions.push(() => governorContract.setProposalCooldown(value));
|
|
@@ -5360,8 +6217,7 @@ var require_operations = __commonJS({
|
|
|
5360
6217
|
signer,
|
|
5361
6218
|
governor,
|
|
5362
6219
|
timelock = null,
|
|
5363
|
-
subdao = null
|
|
5364
|
-
sxxxToken = null
|
|
6220
|
+
subdao = null
|
|
5365
6221
|
}) {
|
|
5366
6222
|
if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
|
|
5367
6223
|
if (!governor) throw new SageSDKError(CODES.INVALID_ARGS, "governor required");
|
|
@@ -5388,29 +6244,11 @@ var require_operations = __commonJS({
|
|
|
5388
6244
|
let mode = null;
|
|
5389
6245
|
let eligible = null;
|
|
5390
6246
|
if (resolvedSubDAO) {
|
|
5391
|
-
const subdaoContract = new Contract(resolvedSubDAO, ["function getGovernanceMode() view returns (uint8)", "function isEligibleMember(address) view returns (bool)"
|
|
6247
|
+
const subdaoContract = new Contract(resolvedSubDAO, ["function getGovernanceMode() view returns (uint8)", "function isEligibleMember(address) view returns (bool)"], provider);
|
|
5392
6248
|
mode = await subdaoContract.getGovernanceMode().catch(() => null);
|
|
5393
6249
|
if (mode === 0 && signerAddr) {
|
|
5394
6250
|
eligible = await subdaoContract.isEligibleMember(signerAddr).catch(() => null);
|
|
5395
6251
|
}
|
|
5396
|
-
if (!sxxxToken) {
|
|
5397
|
-
sxxxToken = await subdaoContract.stakeToken().catch(() => null);
|
|
5398
|
-
if (sxxxToken === ZeroAddress) sxxxToken = null;
|
|
5399
|
-
}
|
|
5400
|
-
}
|
|
5401
|
-
if (!sxxxToken) {
|
|
5402
|
-
sxxxToken = await governorContract.sxxxToken().catch(() => null);
|
|
5403
|
-
if (sxxxToken === ZeroAddress) sxxxToken = null;
|
|
5404
|
-
}
|
|
5405
|
-
let balance = null;
|
|
5406
|
-
let allowance = null;
|
|
5407
|
-
let authorizedBurner = null;
|
|
5408
|
-
const stakeRequired = sxxxToken ? BigInt(50) * BigInt(10) ** BigInt(18) : null;
|
|
5409
|
-
if (sxxxToken && signerAddr) {
|
|
5410
|
-
const tokenContract = new Contract(sxxxToken, ["function balanceOf(address) view returns (uint256)", "function allowance(address,address) view returns (uint256)", "function authorizedBurners(address) view returns (bool)"], provider);
|
|
5411
|
-
balance = await tokenContract.balanceOf(signerAddr).catch(() => null);
|
|
5412
|
-
allowance = await tokenContract.allowance(signerAddr, govAddr).catch(() => null);
|
|
5413
|
-
authorizedBurner = await tokenContract.authorizedBurners(govAddr).catch(() => null);
|
|
5414
6252
|
}
|
|
5415
6253
|
let cooldownSeconds = null;
|
|
5416
6254
|
let cooldownRemaining = null;
|
|
@@ -5440,13 +6278,6 @@ var require_operations = __commonJS({
|
|
|
5440
6278
|
subdao: resolvedSubDAO,
|
|
5441
6279
|
governanceMode: mode,
|
|
5442
6280
|
eligibleMember: eligible,
|
|
5443
|
-
stake: {
|
|
5444
|
-
token: sxxxToken,
|
|
5445
|
-
requiredWei: stakeRequired,
|
|
5446
|
-
balanceWei: balance,
|
|
5447
|
-
allowanceWei: allowance,
|
|
5448
|
-
governorAuthorizedBurner: authorizedBurner
|
|
5449
|
-
},
|
|
5450
6281
|
timelock
|
|
5451
6282
|
};
|
|
5452
6283
|
}
|
|
@@ -5881,13 +6712,16 @@ var require_factory = __commonJS({
|
|
|
5881
6712
|
accessModel: Number(template[2]),
|
|
5882
6713
|
forkPolicy: Number(template[3]),
|
|
5883
6714
|
membershipPolicy: Number(template[4]),
|
|
5884
|
-
|
|
6715
|
+
// template[5] was minStakeAmount (deprecated)
|
|
5885
6716
|
burnAmount: BigInt(template[6].toString()),
|
|
5886
6717
|
votingDelay: BigInt(template[7].toString()),
|
|
5887
6718
|
votingPeriod: BigInt(template[8].toString()),
|
|
5888
6719
|
proposalThreshold: BigInt(template[9].toString()),
|
|
6720
|
+
// template[10] is quorumVotes (absolute token amount, e.g., 100e18 = 100 SXXX)
|
|
6721
|
+
quorumVotes: BigInt(template[10].toString()),
|
|
5889
6722
|
quorumPercentage: Number(template[10]),
|
|
5890
|
-
|
|
6723
|
+
// deprecated alias
|
|
6724
|
+
// template[11] was proposalStakeRequired (deprecated)
|
|
5891
6725
|
proposalCooldownSeconds: BigInt(template[12].toString()),
|
|
5892
6726
|
isActive: Boolean(template[13])
|
|
5893
6727
|
});
|
|
@@ -5908,13 +6742,16 @@ var require_factory = __commonJS({
|
|
|
5908
6742
|
accessModel: Number(template[2]),
|
|
5909
6743
|
forkPolicy: Number(template[3]),
|
|
5910
6744
|
membershipPolicy: Number(template[4]),
|
|
5911
|
-
|
|
6745
|
+
// template[5] was minStakeAmount (deprecated)
|
|
5912
6746
|
burnAmount: BigInt(template[6].toString()),
|
|
5913
6747
|
votingDelay: BigInt(template[7].toString()),
|
|
5914
6748
|
votingPeriod: BigInt(template[8].toString()),
|
|
5915
6749
|
proposalThreshold: BigInt(template[9].toString()),
|
|
6750
|
+
// template[10] is quorumVotes (absolute token amount, e.g., 100e18 = 100 SXXX)
|
|
6751
|
+
quorumVotes: BigInt(template[10].toString()),
|
|
5916
6752
|
quorumPercentage: Number(template[10]),
|
|
5917
|
-
|
|
6753
|
+
// deprecated alias
|
|
6754
|
+
// template[11] was proposalStakeRequired (deprecated)
|
|
5918
6755
|
proposalCooldownSeconds: BigInt(template[12].toString()),
|
|
5919
6756
|
isActive: Boolean(template[13]),
|
|
5920
6757
|
usage: usage != null ? BigInt(usage.toString()) : null,
|
|
@@ -5922,18 +6759,19 @@ var require_factory = __commonJS({
|
|
|
5922
6759
|
};
|
|
5923
6760
|
}
|
|
5924
6761
|
var FactoryWriteInterface = new Interface(ABI.FactoryWrite);
|
|
5925
|
-
function buildCreateSubDAOTx({ factory, name, description, accessModel,
|
|
6762
|
+
function buildCreateSubDAOTx({ factory, name, description, accessModel, burnAmount }) {
|
|
5926
6763
|
const addr = normalise(factory, "factory");
|
|
5927
6764
|
const payload = FactoryWriteInterface.encodeFunctionData("createSubDAO", [
|
|
5928
6765
|
String(name),
|
|
5929
6766
|
String(description),
|
|
5930
6767
|
Number(accessModel),
|
|
5931
|
-
|
|
6768
|
+
0n,
|
|
6769
|
+
// minStakeAmount deprecated
|
|
5932
6770
|
BigInt(burnAmount)
|
|
5933
6771
|
]);
|
|
5934
6772
|
return { to: addr, data: payload, value: 0n };
|
|
5935
6773
|
}
|
|
5936
|
-
function buildCreateSubDAOWithStableTx({ factory, name, description, accessModel,
|
|
6774
|
+
function buildCreateSubDAOWithStableTx({ factory, name, description, accessModel, permit }) {
|
|
5937
6775
|
const addr = normalise(factory, "factory");
|
|
5938
6776
|
if (!permit) throw new SageSDKError(CODES.INVALID_ARGS, "permit required for stable creation");
|
|
5939
6777
|
const tuple = [
|
|
@@ -5947,7 +6785,8 @@ var require_factory = __commonJS({
|
|
|
5947
6785
|
String(name),
|
|
5948
6786
|
String(description),
|
|
5949
6787
|
Number(accessModel),
|
|
5950
|
-
|
|
6788
|
+
0n,
|
|
6789
|
+
// minStakeAmount deprecated
|
|
5951
6790
|
tuple
|
|
5952
6791
|
]);
|
|
5953
6792
|
return { to: addr, data: payload, value: 0n };
|
|
@@ -5989,30 +6828,37 @@ var require_factory = __commonJS({
|
|
|
5989
6828
|
name,
|
|
5990
6829
|
description,
|
|
5991
6830
|
accessModel,
|
|
5992
|
-
minStakeAmount,
|
|
5993
6831
|
burnAmount,
|
|
5994
6832
|
votingDelay,
|
|
5995
6833
|
votingPeriod,
|
|
5996
6834
|
proposalThreshold,
|
|
6835
|
+
quorumVotes,
|
|
5997
6836
|
quorumPercentage,
|
|
6837
|
+
// deprecated alias for quorumVotes
|
|
5998
6838
|
initialForkPolicy,
|
|
5999
6839
|
initialMembershipPolicy,
|
|
6000
|
-
profileCID
|
|
6840
|
+
profileCID,
|
|
6841
|
+
manifestCID,
|
|
6842
|
+
manifestVersion
|
|
6001
6843
|
}) {
|
|
6002
6844
|
const addr = normalise(factory, "factory");
|
|
6845
|
+
const quorum = quorumVotes ?? quorumPercentage;
|
|
6003
6846
|
const payload = FactoryWriteInterface.encodeFunctionData("createSubDAOWithParams", [
|
|
6004
6847
|
String(name),
|
|
6005
6848
|
String(description),
|
|
6006
6849
|
Number(accessModel),
|
|
6007
|
-
|
|
6850
|
+
0n,
|
|
6851
|
+
// minStakeAmount deprecated
|
|
6008
6852
|
BigInt(burnAmount),
|
|
6009
6853
|
BigInt(votingDelay),
|
|
6010
6854
|
BigInt(votingPeriod),
|
|
6011
6855
|
BigInt(proposalThreshold),
|
|
6012
|
-
BigInt(
|
|
6856
|
+
BigInt(quorum),
|
|
6013
6857
|
Number(initialForkPolicy),
|
|
6014
6858
|
Number(initialMembershipPolicy),
|
|
6015
|
-
String(profileCID ?? "")
|
|
6859
|
+
String(profileCID ?? ""),
|
|
6860
|
+
String(manifestCID ?? ""),
|
|
6861
|
+
String(manifestVersion ?? "")
|
|
6016
6862
|
]);
|
|
6017
6863
|
return { to: addr, data: payload, value: 0n };
|
|
6018
6864
|
}
|
|
@@ -6021,7 +6867,6 @@ var require_factory = __commonJS({
|
|
|
6021
6867
|
name,
|
|
6022
6868
|
description,
|
|
6023
6869
|
accessModel,
|
|
6024
|
-
minStakeAmount,
|
|
6025
6870
|
operatorExecutor,
|
|
6026
6871
|
operatorAdmin,
|
|
6027
6872
|
permit
|
|
@@ -6039,7 +6884,8 @@ var require_factory = __commonJS({
|
|
|
6039
6884
|
String(name),
|
|
6040
6885
|
String(description),
|
|
6041
6886
|
Number(accessModel),
|
|
6042
|
-
|
|
6887
|
+
0n,
|
|
6888
|
+
// minStakeAmount deprecated
|
|
6043
6889
|
normalise(operatorExecutor, "operatorExecutor"),
|
|
6044
6890
|
normalise(operatorAdmin, "operatorAdmin"),
|
|
6045
6891
|
tuple
|
|
@@ -6051,7 +6897,6 @@ var require_factory = __commonJS({
|
|
|
6051
6897
|
name,
|
|
6052
6898
|
description,
|
|
6053
6899
|
accessModel,
|
|
6054
|
-
minStakeAmount,
|
|
6055
6900
|
operatorExecutor,
|
|
6056
6901
|
operatorAdmin,
|
|
6057
6902
|
anyoneExec = false,
|
|
@@ -6071,7 +6916,8 @@ var require_factory = __commonJS({
|
|
|
6071
6916
|
String(name),
|
|
6072
6917
|
String(description),
|
|
6073
6918
|
Number(accessModel),
|
|
6074
|
-
|
|
6919
|
+
0n,
|
|
6920
|
+
// minStakeAmount deprecated
|
|
6075
6921
|
normalise(operatorExecutor, "operatorExecutor"),
|
|
6076
6922
|
normalise(operatorAdmin, "operatorAdmin"),
|
|
6077
6923
|
Boolean(anyoneExec),
|
|
@@ -7007,7 +7853,7 @@ var require_ipns = __commonJS({
|
|
|
7007
7853
|
}
|
|
7008
7854
|
const worker = workerConfig ? {
|
|
7009
7855
|
baseUrl: removeTrailingSlash(workerConfig.baseUrl || workerConfig.url || workerConfig.workerBaseUrl || ""),
|
|
7010
|
-
challengePath: ensureLeadingSlash(workerConfig.challengePath || workerConfig.challenge || "/auth/challenge"),
|
|
7856
|
+
challengePath: ensureLeadingSlash(workerConfig.challengePath || workerConfig.challenge || "/ipfs/auth/challenge"),
|
|
7011
7857
|
publishPath: ensureLeadingSlash(workerConfig.publishPath || workerConfig.publish || "/ipns/publish"),
|
|
7012
7858
|
token: workerConfig.token || null,
|
|
7013
7859
|
getAuth: workerConfig.getAuth || null,
|
|
@@ -7439,51 +8285,345 @@ var require_access = __commonJS({
|
|
|
7439
8285
|
if (!response || !response.decryptedData) {
|
|
7440
8286
|
throw new Error("[personal] Lit decryption failed: missing decryptedData");
|
|
7441
8287
|
}
|
|
7442
|
-
return uint8arrayToString(response.decryptedData, "utf8");
|
|
8288
|
+
return uint8arrayToString(response.decryptedData, "utf8");
|
|
8289
|
+
}
|
|
8290
|
+
module2.exports = {
|
|
8291
|
+
resolveEncryptedResource,
|
|
8292
|
+
decryptWithLit
|
|
8293
|
+
};
|
|
8294
|
+
}
|
|
8295
|
+
});
|
|
8296
|
+
|
|
8297
|
+
// src/personal/index.js
|
|
8298
|
+
var require_personal = __commonJS({
|
|
8299
|
+
"src/personal/index.js"(exports2, module2) {
|
|
8300
|
+
var { Interface } = __require("ethers");
|
|
8301
|
+
var ABI = require_abi();
|
|
8302
|
+
var { normaliseAddress, toBytes32Key } = require_helpers();
|
|
8303
|
+
function buildCreatePersonalRegistryTx({ factory, policy }) {
|
|
8304
|
+
const to = normaliseAddress(factory, "factory");
|
|
8305
|
+
const iface = new Interface(ABI.PersonalLibraryFacet);
|
|
8306
|
+
const data = iface.encodeFunctionData("createPersonalRegistry", [policy >>> 0]);
|
|
8307
|
+
return { to, data, value: 0n };
|
|
8308
|
+
}
|
|
8309
|
+
function buildSetPriceTx({ marketplace, key, price }) {
|
|
8310
|
+
const to = normaliseAddress(marketplace, "marketplace");
|
|
8311
|
+
const k = toBytes32Key(key);
|
|
8312
|
+
const iface = new Interface(ABI.PersonalMarketplace);
|
|
8313
|
+
const data = iface.encodeFunctionData("setPrice", [k, BigInt(price)]);
|
|
8314
|
+
return { to, data, value: 0n };
|
|
8315
|
+
}
|
|
8316
|
+
function buildBuyTx({ marketplace, creator, key, expectedPrice, deadline }) {
|
|
8317
|
+
const to = normaliseAddress(marketplace, "marketplace");
|
|
8318
|
+
const iface = new Interface(ABI.PersonalMarketplace);
|
|
8319
|
+
const data = iface.encodeFunctionData("purchase", [
|
|
8320
|
+
normaliseAddress(creator, "creator"),
|
|
8321
|
+
toBytes32Key(key),
|
|
8322
|
+
BigInt(expectedPrice),
|
|
8323
|
+
BigInt(deadline)
|
|
8324
|
+
]);
|
|
8325
|
+
return { to, data, value: 0n };
|
|
8326
|
+
}
|
|
8327
|
+
module2.exports = {
|
|
8328
|
+
buildCreatePersonalRegistryTx,
|
|
8329
|
+
buildSetPriceTx,
|
|
8330
|
+
buildBuyTx,
|
|
8331
|
+
...require_receipt(),
|
|
8332
|
+
...require_access()
|
|
8333
|
+
};
|
|
8334
|
+
}
|
|
8335
|
+
});
|
|
8336
|
+
|
|
8337
|
+
// src/premium/index.js
|
|
8338
|
+
var require_premium = __commonJS({
|
|
8339
|
+
"src/premium/index.js"(exports2, module2) {
|
|
8340
|
+
var axios = __require("axios");
|
|
8341
|
+
var { getAddress } = __require("ethers");
|
|
8342
|
+
function safeGetAddress(value) {
|
|
8343
|
+
try {
|
|
8344
|
+
return getAddress(value);
|
|
8345
|
+
} catch {
|
|
8346
|
+
return null;
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
function mapSafe(list, mapper) {
|
|
8350
|
+
const out = [];
|
|
8351
|
+
for (const item of list || []) {
|
|
8352
|
+
try {
|
|
8353
|
+
const v = mapper(item);
|
|
8354
|
+
if (v != null) out.push(v);
|
|
8355
|
+
} catch {
|
|
8356
|
+
}
|
|
8357
|
+
}
|
|
8358
|
+
return out;
|
|
8359
|
+
}
|
|
8360
|
+
async function query(url, document, variables) {
|
|
8361
|
+
if (!url) throw new Error("subgraph url required");
|
|
8362
|
+
const resp = await axios.post(url, { query: document, variables }, { timeout: 1e4 });
|
|
8363
|
+
if (resp.data && resp.data.errors) {
|
|
8364
|
+
throw new Error(resp.data.errors.map((e) => e.message).join("; "));
|
|
8365
|
+
}
|
|
8366
|
+
return resp.data.data;
|
|
8367
|
+
}
|
|
8368
|
+
async function listPremiumPrompts({ url, subdao = null, first = 50, skip = 0, orderBy = "blockTimestamp", orderDirection = "desc" }) {
|
|
8369
|
+
const safeOrderBy = ["blockTimestamp", "price"].includes(orderBy) ? orderBy : "blockTimestamp";
|
|
8370
|
+
const safeOrderDirection = orderDirection === "asc" ? "asc" : "desc";
|
|
8371
|
+
let doc;
|
|
8372
|
+
const variables = {
|
|
8373
|
+
first: Math.min(Math.max(1, Number(first || 50)), 200),
|
|
8374
|
+
skip: Number(skip || 0)
|
|
8375
|
+
};
|
|
8376
|
+
if (subdao) {
|
|
8377
|
+
const addr = safeGetAddress(subdao);
|
|
8378
|
+
if (!addr) throw new Error("invalid subdao address");
|
|
8379
|
+
variables.subdao = addr.toLowerCase();
|
|
8380
|
+
doc = `
|
|
8381
|
+
query($subdao: Bytes!, $first: Int!, $skip: Int!) {
|
|
8382
|
+
premiumPrompts(
|
|
8383
|
+
where: { subdao: $subdao }
|
|
8384
|
+
first: $first
|
|
8385
|
+
skip: $skip
|
|
8386
|
+
orderBy: ${safeOrderBy}
|
|
8387
|
+
orderDirection: ${safeOrderDirection}
|
|
8388
|
+
) {
|
|
8389
|
+
id
|
|
8390
|
+
cidHash
|
|
8391
|
+
subdao
|
|
8392
|
+
price
|
|
8393
|
+
manifestCID
|
|
8394
|
+
blockNumber
|
|
8395
|
+
blockTimestamp
|
|
8396
|
+
transactionHash
|
|
8397
|
+
}
|
|
8398
|
+
}
|
|
8399
|
+
`;
|
|
8400
|
+
} else {
|
|
8401
|
+
doc = `
|
|
8402
|
+
query($first: Int!, $skip: Int!) {
|
|
8403
|
+
premiumPrompts(
|
|
8404
|
+
first: $first
|
|
8405
|
+
skip: $skip
|
|
8406
|
+
orderBy: ${safeOrderBy}
|
|
8407
|
+
orderDirection: ${safeOrderDirection}
|
|
8408
|
+
) {
|
|
8409
|
+
id
|
|
8410
|
+
cidHash
|
|
8411
|
+
subdao
|
|
8412
|
+
price
|
|
8413
|
+
manifestCID
|
|
8414
|
+
blockNumber
|
|
8415
|
+
blockTimestamp
|
|
8416
|
+
transactionHash
|
|
8417
|
+
}
|
|
8418
|
+
}
|
|
8419
|
+
`;
|
|
8420
|
+
}
|
|
8421
|
+
const data = await query(url, doc, variables);
|
|
8422
|
+
return mapSafe(data?.premiumPrompts, (p) => {
|
|
8423
|
+
const sub = safeGetAddress(p.subdao);
|
|
8424
|
+
if (!sub) return null;
|
|
8425
|
+
return {
|
|
8426
|
+
id: String(p.id),
|
|
8427
|
+
cidHash: String(p.cidHash),
|
|
8428
|
+
subdao: sub,
|
|
8429
|
+
price: BigInt(String(p.price || "0")),
|
|
8430
|
+
manifestCID: p.manifestCID || null,
|
|
8431
|
+
blockNumber: Number(p.blockNumber || 0),
|
|
8432
|
+
blockTimestamp: Number(p.blockTimestamp || 0),
|
|
8433
|
+
transactionHash: p.transactionHash || null
|
|
8434
|
+
};
|
|
8435
|
+
});
|
|
8436
|
+
}
|
|
8437
|
+
async function getPremiumPrompt({ url, cidHash }) {
|
|
8438
|
+
const id2 = cidHash.toLowerCase().startsWith("0x") ? cidHash.toLowerCase() : `0x${cidHash.toLowerCase()}`;
|
|
8439
|
+
const doc = `
|
|
8440
|
+
query($id: ID!) {
|
|
8441
|
+
premiumPrompt(id: $id) {
|
|
8442
|
+
id
|
|
8443
|
+
cidHash
|
|
8444
|
+
subdao
|
|
8445
|
+
price
|
|
8446
|
+
manifestCID
|
|
8447
|
+
blockNumber
|
|
8448
|
+
blockTimestamp
|
|
8449
|
+
transactionHash
|
|
8450
|
+
}
|
|
8451
|
+
premiumPromptStats(id: $id) {
|
|
8452
|
+
totalRevenue
|
|
8453
|
+
purchaseCount
|
|
8454
|
+
uniqueBuyers
|
|
8455
|
+
currentHolders
|
|
8456
|
+
}
|
|
8457
|
+
}
|
|
8458
|
+
`;
|
|
8459
|
+
const data = await query(url, doc, { id: id2 });
|
|
8460
|
+
const p = data?.premiumPrompt;
|
|
8461
|
+
if (!p) return null;
|
|
8462
|
+
const sub = safeGetAddress(p.subdao);
|
|
8463
|
+
if (!sub) return null;
|
|
8464
|
+
const stats = data?.premiumPromptStats || {};
|
|
8465
|
+
return {
|
|
8466
|
+
id: String(p.id),
|
|
8467
|
+
cidHash: String(p.cidHash),
|
|
8468
|
+
subdao: sub,
|
|
8469
|
+
price: BigInt(String(p.price || "0")),
|
|
8470
|
+
manifestCID: p.manifestCID || null,
|
|
8471
|
+
blockNumber: Number(p.blockNumber || 0),
|
|
8472
|
+
blockTimestamp: Number(p.blockTimestamp || 0),
|
|
8473
|
+
transactionHash: p.transactionHash || null,
|
|
8474
|
+
// Stats
|
|
8475
|
+
totalRevenue: stats.totalRevenue ? BigInt(String(stats.totalRevenue)) : null,
|
|
8476
|
+
purchaseCount: stats.purchaseCount ? BigInt(String(stats.purchaseCount)) : null,
|
|
8477
|
+
uniqueBuyers: stats.uniqueBuyers ? BigInt(String(stats.uniqueBuyers)) : null,
|
|
8478
|
+
currentHolders: stats.currentHolders ? BigInt(String(stats.currentHolders)) : null
|
|
8479
|
+
};
|
|
8480
|
+
}
|
|
8481
|
+
async function listPremiumPurchases({ url, cidHash, first = 50, skip = 0 }) {
|
|
8482
|
+
const normalizedCidHash = cidHash.toLowerCase().startsWith("0x") ? cidHash.toLowerCase() : `0x${cidHash.toLowerCase()}`;
|
|
8483
|
+
const doc = `
|
|
8484
|
+
query($cidHash: Bytes!, $first: Int!, $skip: Int!) {
|
|
8485
|
+
premiumPurchases(
|
|
8486
|
+
where: { cidHash: $cidHash }
|
|
8487
|
+
first: $first
|
|
8488
|
+
skip: $skip
|
|
8489
|
+
orderBy: blockTimestamp
|
|
8490
|
+
orderDirection: desc
|
|
8491
|
+
) {
|
|
8492
|
+
id
|
|
8493
|
+
cidHash
|
|
8494
|
+
buyer
|
|
8495
|
+
price
|
|
8496
|
+
treasury
|
|
8497
|
+
blockNumber
|
|
8498
|
+
blockTimestamp
|
|
8499
|
+
transactionHash
|
|
8500
|
+
}
|
|
8501
|
+
}
|
|
8502
|
+
`;
|
|
8503
|
+
const data = await query(url, doc, {
|
|
8504
|
+
cidHash: normalizedCidHash,
|
|
8505
|
+
first: Math.min(Math.max(1, Number(first || 50)), 200),
|
|
8506
|
+
skip: Number(skip || 0)
|
|
8507
|
+
});
|
|
8508
|
+
return mapSafe(data?.premiumPurchases, (p) => {
|
|
8509
|
+
const buyer = safeGetAddress(p.buyer);
|
|
8510
|
+
const treasury = safeGetAddress(p.treasury);
|
|
8511
|
+
if (!buyer || !treasury) return null;
|
|
8512
|
+
return {
|
|
8513
|
+
id: String(p.id),
|
|
8514
|
+
cidHash: String(p.cidHash),
|
|
8515
|
+
buyer,
|
|
8516
|
+
price: BigInt(String(p.price || "0")),
|
|
8517
|
+
treasury,
|
|
8518
|
+
blockNumber: Number(p.blockNumber || 0),
|
|
8519
|
+
blockTimestamp: Number(p.blockTimestamp || 0),
|
|
8520
|
+
transactionHash: p.transactionHash || null
|
|
8521
|
+
};
|
|
8522
|
+
});
|
|
8523
|
+
}
|
|
8524
|
+
async function hasPurchased({ url, cidHash, holder }) {
|
|
8525
|
+
const normalizedCidHash = cidHash.toLowerCase().startsWith("0x") ? cidHash.toLowerCase() : `0x${cidHash.toLowerCase()}`;
|
|
8526
|
+
const normalizedHolder = holder.toLowerCase();
|
|
8527
|
+
const id2 = `${normalizedCidHash}-${normalizedHolder}`;
|
|
8528
|
+
const doc = `
|
|
8529
|
+
query($id: ID!) {
|
|
8530
|
+
premiumHolderBalance(id: $id) {
|
|
8531
|
+
balance
|
|
8532
|
+
}
|
|
8533
|
+
}
|
|
8534
|
+
`;
|
|
8535
|
+
const data = await query(url, doc, { id: id2 });
|
|
8536
|
+
const balance = data?.premiumHolderBalance?.balance;
|
|
8537
|
+
return balance ? BigInt(String(balance)) > 0n : false;
|
|
8538
|
+
}
|
|
8539
|
+
async function getHolderBalance({ url, cidHash, holder }) {
|
|
8540
|
+
const normalizedCidHash = cidHash.toLowerCase().startsWith("0x") ? cidHash.toLowerCase() : `0x${cidHash.toLowerCase()}`;
|
|
8541
|
+
const normalizedHolder = holder.toLowerCase();
|
|
8542
|
+
const id2 = `${normalizedCidHash}-${normalizedHolder}`;
|
|
8543
|
+
const doc = `
|
|
8544
|
+
query($id: ID!) {
|
|
8545
|
+
premiumHolderBalance(id: $id) {
|
|
8546
|
+
balance
|
|
8547
|
+
updatedAt
|
|
8548
|
+
}
|
|
8549
|
+
}
|
|
8550
|
+
`;
|
|
8551
|
+
const data = await query(url, doc, { id: id2 });
|
|
8552
|
+
const row = data?.premiumHolderBalance;
|
|
8553
|
+
if (!row) return 0n;
|
|
8554
|
+
return BigInt(String(row.balance || "0"));
|
|
8555
|
+
}
|
|
8556
|
+
async function listHolders({ url, cidHash, first = 100, skip = 0 }) {
|
|
8557
|
+
const normalizedCidHash = cidHash.toLowerCase().startsWith("0x") ? cidHash.toLowerCase() : `0x${cidHash.toLowerCase()}`;
|
|
8558
|
+
const doc = `
|
|
8559
|
+
query($cidHash: Bytes!, $first: Int!, $skip: Int!) {
|
|
8560
|
+
premiumHolderBalances(
|
|
8561
|
+
where: { cidHash: $cidHash, balance_gt: "0" }
|
|
8562
|
+
first: $first
|
|
8563
|
+
skip: $skip
|
|
8564
|
+
orderBy: balance
|
|
8565
|
+
orderDirection: desc
|
|
8566
|
+
) {
|
|
8567
|
+
id
|
|
8568
|
+
cidHash
|
|
8569
|
+
holder
|
|
8570
|
+
balance
|
|
8571
|
+
updatedAt
|
|
8572
|
+
}
|
|
7443
8573
|
}
|
|
7444
|
-
|
|
7445
|
-
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
});
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7455
|
-
|
|
7456
|
-
|
|
7457
|
-
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
return { to, data, value: 0n };
|
|
8574
|
+
`;
|
|
8575
|
+
const data = await query(url, doc, {
|
|
8576
|
+
cidHash: normalizedCidHash,
|
|
8577
|
+
first: Math.min(Math.max(1, Number(first || 100)), 500),
|
|
8578
|
+
skip: Number(skip || 0)
|
|
8579
|
+
});
|
|
8580
|
+
return mapSafe(data?.premiumHolderBalances, (h) => {
|
|
8581
|
+
const holder = safeGetAddress(h.holder);
|
|
8582
|
+
if (!holder) return null;
|
|
8583
|
+
return {
|
|
8584
|
+
id: String(h.id),
|
|
8585
|
+
cidHash: String(h.cidHash),
|
|
8586
|
+
holder,
|
|
8587
|
+
balance: BigInt(String(h.balance || "0")),
|
|
8588
|
+
updatedAt: Number(h.updatedAt || 0)
|
|
8589
|
+
};
|
|
8590
|
+
});
|
|
7462
8591
|
}
|
|
7463
|
-
function
|
|
7464
|
-
const
|
|
7465
|
-
|
|
7466
|
-
const
|
|
7467
|
-
|
|
7468
|
-
|
|
8592
|
+
async function getRevSplit({ url, subdao }) {
|
|
8593
|
+
const addr = safeGetAddress(subdao);
|
|
8594
|
+
if (!addr) throw new Error("invalid subdao address");
|
|
8595
|
+
const doc = `
|
|
8596
|
+
query($id: ID!) {
|
|
8597
|
+
premiumRevSplit(id: $id) {
|
|
8598
|
+
id
|
|
8599
|
+
subdao
|
|
8600
|
+
treasuryBps
|
|
8601
|
+
protocolBps
|
|
8602
|
+
creatorBps
|
|
8603
|
+
updatedAt
|
|
8604
|
+
}
|
|
7469
8605
|
}
|
|
7470
|
-
|
|
7471
|
-
const
|
|
7472
|
-
const
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
8606
|
+
`;
|
|
8607
|
+
const data = await query(url, doc, { id: addr.toLowerCase() });
|
|
8608
|
+
const split = data?.premiumRevSplit;
|
|
8609
|
+
if (!split) return null;
|
|
8610
|
+
return {
|
|
8611
|
+
id: String(split.id),
|
|
8612
|
+
subdao: safeGetAddress(split.subdao) || addr,
|
|
8613
|
+
treasuryBps: Number(split.treasuryBps || 0),
|
|
8614
|
+
protocolBps: Number(split.protocolBps || 0),
|
|
8615
|
+
creatorBps: Number(split.creatorBps || 0),
|
|
8616
|
+
updatedAt: Number(split.updatedAt || 0)
|
|
8617
|
+
};
|
|
7480
8618
|
}
|
|
7481
8619
|
module2.exports = {
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
8620
|
+
listPremiumPrompts,
|
|
8621
|
+
getPremiumPrompt,
|
|
8622
|
+
listPremiumPurchases,
|
|
8623
|
+
hasPurchased,
|
|
8624
|
+
getHolderBalance,
|
|
8625
|
+
listHolders,
|
|
8626
|
+
getRevSplit
|
|
7487
8627
|
};
|
|
7488
8628
|
}
|
|
7489
8629
|
});
|
|
@@ -8655,6 +9795,335 @@ var require_contributions = __commonJS({
|
|
|
8655
9795
|
}
|
|
8656
9796
|
});
|
|
8657
9797
|
|
|
9798
|
+
// src/browser/reputation.js
|
|
9799
|
+
var require_reputation = __commonJS({
|
|
9800
|
+
"src/browser/reputation.js"(exports2, module2) {
|
|
9801
|
+
var { getAddress } = require_utils();
|
|
9802
|
+
var subgraph = require_subgraph2();
|
|
9803
|
+
function safeGetAddress(value) {
|
|
9804
|
+
try {
|
|
9805
|
+
return getAddress(value);
|
|
9806
|
+
} catch {
|
|
9807
|
+
return null;
|
|
9808
|
+
}
|
|
9809
|
+
}
|
|
9810
|
+
function clampInt(value, { min = 1, max = 100, fallback = 100 } = {}) {
|
|
9811
|
+
const n = Number(value);
|
|
9812
|
+
if (!Number.isFinite(n)) return fallback;
|
|
9813
|
+
return Math.min(max, Math.max(min, Math.trunc(n)));
|
|
9814
|
+
}
|
|
9815
|
+
function toBigIntString(value, fallback = "0") {
|
|
9816
|
+
try {
|
|
9817
|
+
if (typeof value === "bigint") return value.toString();
|
|
9818
|
+
if (typeof value === "number") return String(Math.trunc(value));
|
|
9819
|
+
const str = String(value).trim();
|
|
9820
|
+
if (!str) return fallback;
|
|
9821
|
+
if (!/^-?\d+$/.test(str)) return fallback;
|
|
9822
|
+
return str;
|
|
9823
|
+
} catch {
|
|
9824
|
+
return fallback;
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
function mapSafe(list, mapper) {
|
|
9828
|
+
const out = [];
|
|
9829
|
+
for (const item of list || []) {
|
|
9830
|
+
try {
|
|
9831
|
+
const v = mapper(item);
|
|
9832
|
+
if (v != null) out.push(v);
|
|
9833
|
+
} catch {
|
|
9834
|
+
}
|
|
9835
|
+
}
|
|
9836
|
+
return out;
|
|
9837
|
+
}
|
|
9838
|
+
async function getBadgesByRecipient({ url, recipient, first = 50 }) {
|
|
9839
|
+
if (!url) throw new Error("subgraph url required");
|
|
9840
|
+
if (!recipient) throw new Error("recipient required");
|
|
9841
|
+
const addr = safeGetAddress(recipient);
|
|
9842
|
+
if (!addr) throw new Error("invalid recipient address");
|
|
9843
|
+
const doc = `
|
|
9844
|
+
query($recipient: Bytes!, $first: Int!) {
|
|
9845
|
+
soulboundBadges(
|
|
9846
|
+
where: { recipient: $recipient }
|
|
9847
|
+
first: $first
|
|
9848
|
+
orderBy: blockTimestamp
|
|
9849
|
+
orderDirection: desc
|
|
9850
|
+
) {
|
|
9851
|
+
id
|
|
9852
|
+
contract
|
|
9853
|
+
badgeId
|
|
9854
|
+
recipient
|
|
9855
|
+
evidenceURI
|
|
9856
|
+
blockNumber
|
|
9857
|
+
blockTimestamp
|
|
9858
|
+
transactionHash
|
|
9859
|
+
}
|
|
9860
|
+
}
|
|
9861
|
+
`;
|
|
9862
|
+
const data = await subgraph.query(url, doc, {
|
|
9863
|
+
recipient: addr.toLowerCase(),
|
|
9864
|
+
first: clampInt(first, { min: 1, max: 100, fallback: 50 })
|
|
9865
|
+
});
|
|
9866
|
+
return mapSafe(data?.soulboundBadges, (row) => {
|
|
9867
|
+
const contract = safeGetAddress(row.contract);
|
|
9868
|
+
const recipientAddr = safeGetAddress(row.recipient);
|
|
9869
|
+
if (!contract || !recipientAddr) return null;
|
|
9870
|
+
return {
|
|
9871
|
+
id: String(row.id),
|
|
9872
|
+
contract,
|
|
9873
|
+
badgeId: toBigIntString(row.badgeId, "0"),
|
|
9874
|
+
recipient: recipientAddr,
|
|
9875
|
+
evidenceURI: row.evidenceURI || null,
|
|
9876
|
+
blockNumber: Number(row.blockNumber || 0),
|
|
9877
|
+
blockTimestamp: Number(row.blockTimestamp || 0),
|
|
9878
|
+
transactionHash: row.transactionHash || null
|
|
9879
|
+
};
|
|
9880
|
+
});
|
|
9881
|
+
}
|
|
9882
|
+
async function listContributions({ url, contributor, subdao, first = 50, orderBy = "updatedAt", orderDirection = "desc" }) {
|
|
9883
|
+
if (!url) throw new Error("subgraph url required");
|
|
9884
|
+
const filters = [];
|
|
9885
|
+
if (contributor) {
|
|
9886
|
+
const addr = safeGetAddress(contributor);
|
|
9887
|
+
if (!addr) throw new Error("invalid contributor address");
|
|
9888
|
+
filters.push(`contributor: "${addr.toLowerCase()}"`);
|
|
9889
|
+
}
|
|
9890
|
+
if (subdao) {
|
|
9891
|
+
const addr = safeGetAddress(subdao);
|
|
9892
|
+
if (!addr) throw new Error("invalid subdao address");
|
|
9893
|
+
filters.push(`dao: "${addr.toLowerCase()}"`);
|
|
9894
|
+
}
|
|
9895
|
+
const where = filters.length ? `where: { ${filters.join(", ")} }` : "";
|
|
9896
|
+
const safeOrderBy = ["updatedAt", "createdAt"].includes(orderBy) ? orderBy : "updatedAt";
|
|
9897
|
+
const safeOrderDirection = orderDirection === "asc" ? "asc" : "desc";
|
|
9898
|
+
const doc = `
|
|
9899
|
+
query($first: Int!) {
|
|
9900
|
+
promptContributions(
|
|
9901
|
+
${where}
|
|
9902
|
+
first: $first
|
|
9903
|
+
orderBy: ${safeOrderBy}
|
|
9904
|
+
orderDirection: ${safeOrderDirection}
|
|
9905
|
+
) {
|
|
9906
|
+
id
|
|
9907
|
+
dao
|
|
9908
|
+
promptKey
|
|
9909
|
+
contributor
|
|
9910
|
+
cid
|
|
9911
|
+
proposalId
|
|
9912
|
+
forVotes
|
|
9913
|
+
againstVotes
|
|
9914
|
+
abstainVotes
|
|
9915
|
+
uniqueVoters
|
|
9916
|
+
quorum
|
|
9917
|
+
fromBounty
|
|
9918
|
+
bountyId
|
|
9919
|
+
badgeId
|
|
9920
|
+
badgeEvidenceURI
|
|
9921
|
+
createdAt
|
|
9922
|
+
updatedAt
|
|
9923
|
+
transactionHash
|
|
9924
|
+
}
|
|
9925
|
+
}
|
|
9926
|
+
`;
|
|
9927
|
+
const data = await subgraph.query(url, doc, {
|
|
9928
|
+
first: clampInt(first, { min: 1, max: 100, fallback: 50 })
|
|
9929
|
+
});
|
|
9930
|
+
return mapSafe(data?.promptContributions, (row) => {
|
|
9931
|
+
const dao = safeGetAddress(row.dao);
|
|
9932
|
+
const contributorAddr = safeGetAddress(row.contributor);
|
|
9933
|
+
if (!dao) return null;
|
|
9934
|
+
return {
|
|
9935
|
+
id: String(row.id),
|
|
9936
|
+
dao,
|
|
9937
|
+
promptKey: String(row.promptKey),
|
|
9938
|
+
contributor: contributorAddr || "0x0000000000000000000000000000000000000000",
|
|
9939
|
+
cid: String(row.cid),
|
|
9940
|
+
timestamp: Number(row.updatedAt || 0),
|
|
9941
|
+
createdAt: Number(row.createdAt || 0),
|
|
9942
|
+
updatedAt: Number(row.updatedAt || 0),
|
|
9943
|
+
transactionHash: row.transactionHash || null,
|
|
9944
|
+
proposalId: row.proposalId != null ? toBigIntString(row.proposalId) : null,
|
|
9945
|
+
forVotes: row.forVotes != null ? toBigIntString(row.forVotes) : null,
|
|
9946
|
+
againstVotes: row.againstVotes != null ? toBigIntString(row.againstVotes) : null,
|
|
9947
|
+
abstainVotes: row.abstainVotes != null ? toBigIntString(row.abstainVotes) : null,
|
|
9948
|
+
uniqueVoters: row.uniqueVoters != null ? Number(row.uniqueVoters) : null,
|
|
9949
|
+
quorum: row.quorum != null ? toBigIntString(row.quorum) : null,
|
|
9950
|
+
fromBounty: Boolean(row.fromBounty),
|
|
9951
|
+
bountyId: row.bountyId != null ? toBigIntString(row.bountyId) : null,
|
|
9952
|
+
badgeId: row.badgeId != null ? toBigIntString(row.badgeId) : null,
|
|
9953
|
+
badgeEvidenceURI: row.badgeEvidenceURI || null
|
|
9954
|
+
};
|
|
9955
|
+
});
|
|
9956
|
+
}
|
|
9957
|
+
function computeAggregates(contributions) {
|
|
9958
|
+
if (!contributions || contributions.length === 0) {
|
|
9959
|
+
return {
|
|
9960
|
+
totalContributions: 0,
|
|
9961
|
+
uniqueContributors: 0,
|
|
9962
|
+
bountyOriginated: 0,
|
|
9963
|
+
governanceOriginated: 0,
|
|
9964
|
+
totalForVotes: "0",
|
|
9965
|
+
totalAgainstVotes: "0",
|
|
9966
|
+
averageVoterCount: 0,
|
|
9967
|
+
badgeCount: 0
|
|
9968
|
+
};
|
|
9969
|
+
}
|
|
9970
|
+
const contributorSet = /* @__PURE__ */ new Set();
|
|
9971
|
+
let bountyOriginated = 0;
|
|
9972
|
+
let governanceOriginated = 0;
|
|
9973
|
+
let totalForVotes = 0n;
|
|
9974
|
+
let totalAgainstVotes = 0n;
|
|
9975
|
+
let voterCountSum = 0;
|
|
9976
|
+
let voterCountN = 0;
|
|
9977
|
+
let badgeCount = 0;
|
|
9978
|
+
for (const c of contributions) {
|
|
9979
|
+
if (c.contributor) contributorSet.add(c.contributor.toLowerCase());
|
|
9980
|
+
if (c.fromBounty) {
|
|
9981
|
+
bountyOriginated++;
|
|
9982
|
+
} else {
|
|
9983
|
+
governanceOriginated++;
|
|
9984
|
+
}
|
|
9985
|
+
if (c.forVotes != null) {
|
|
9986
|
+
try {
|
|
9987
|
+
totalForVotes += BigInt(c.forVotes);
|
|
9988
|
+
} catch {
|
|
9989
|
+
}
|
|
9990
|
+
}
|
|
9991
|
+
if (c.againstVotes != null) {
|
|
9992
|
+
try {
|
|
9993
|
+
totalAgainstVotes += BigInt(c.againstVotes);
|
|
9994
|
+
} catch {
|
|
9995
|
+
}
|
|
9996
|
+
}
|
|
9997
|
+
if (c.uniqueVoters != null) {
|
|
9998
|
+
voterCountSum += c.uniqueVoters;
|
|
9999
|
+
voterCountN++;
|
|
10000
|
+
}
|
|
10001
|
+
if (c.badgeId != null) badgeCount++;
|
|
10002
|
+
}
|
|
10003
|
+
return {
|
|
10004
|
+
totalContributions: contributions.length,
|
|
10005
|
+
uniqueContributors: contributorSet.size,
|
|
10006
|
+
bountyOriginated,
|
|
10007
|
+
governanceOriginated,
|
|
10008
|
+
totalForVotes: totalForVotes.toString(),
|
|
10009
|
+
totalAgainstVotes: totalAgainstVotes.toString(),
|
|
10010
|
+
averageVoterCount: voterCountN > 0 ? Math.round(voterCountSum / voterCountN) : 0,
|
|
10011
|
+
badgeCount
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
10014
|
+
async function getByAddress({
|
|
10015
|
+
url,
|
|
10016
|
+
address,
|
|
10017
|
+
subdao,
|
|
10018
|
+
includeBadges = true,
|
|
10019
|
+
includeContributions = true,
|
|
10020
|
+
first = 100
|
|
10021
|
+
} = {}) {
|
|
10022
|
+
if (!url) throw new Error("subgraph url required");
|
|
10023
|
+
if (!address) throw new Error("address required");
|
|
10024
|
+
const addr = safeGetAddress(address);
|
|
10025
|
+
if (!addr) throw new Error("invalid address");
|
|
10026
|
+
const subdaoAddr = subdao ? safeGetAddress(subdao) : null;
|
|
10027
|
+
if (subdao && !subdaoAddr) throw new Error("invalid subdao address");
|
|
10028
|
+
const limit = clampInt(first, { min: 1, max: 200, fallback: 100 });
|
|
10029
|
+
let badges = void 0;
|
|
10030
|
+
if (includeBadges) {
|
|
10031
|
+
badges = await getBadgesByRecipient({ url, recipient: addr, first: limit });
|
|
10032
|
+
}
|
|
10033
|
+
let contributionAggregates = void 0;
|
|
10034
|
+
let lastContributionAt = null;
|
|
10035
|
+
if (includeContributions) {
|
|
10036
|
+
const rows = await listContributions({
|
|
10037
|
+
url,
|
|
10038
|
+
contributor: addr,
|
|
10039
|
+
subdao: subdaoAddr || void 0,
|
|
10040
|
+
first: limit,
|
|
10041
|
+
orderBy: "updatedAt",
|
|
10042
|
+
orderDirection: "desc"
|
|
10043
|
+
});
|
|
10044
|
+
if (rows && rows.length) {
|
|
10045
|
+
const updatedAt = rows[0]?.updatedAt ?? rows[0]?.timestamp ?? null;
|
|
10046
|
+
lastContributionAt = updatedAt != null ? Number(updatedAt) : null;
|
|
10047
|
+
if (!Number.isFinite(lastContributionAt)) lastContributionAt = null;
|
|
10048
|
+
}
|
|
10049
|
+
contributionAggregates = computeAggregates(rows || []);
|
|
10050
|
+
}
|
|
10051
|
+
const signals = {
|
|
10052
|
+
badgeCount: includeBadges ? badges ? badges.length : 0 : null,
|
|
10053
|
+
totalContributions: includeContributions ? contributionAggregates ? contributionAggregates.totalContributions : 0 : null,
|
|
10054
|
+
governanceOriginated: includeContributions ? contributionAggregates ? contributionAggregates.governanceOriginated : 0 : null,
|
|
10055
|
+
bountyOriginated: includeContributions ? contributionAggregates ? contributionAggregates.bountyOriginated : 0 : null,
|
|
10056
|
+
lastContributionAt: includeContributions ? lastContributionAt : null
|
|
10057
|
+
};
|
|
10058
|
+
const out = {
|
|
10059
|
+
address: addr,
|
|
10060
|
+
subdao: subdaoAddr || null,
|
|
10061
|
+
signals
|
|
10062
|
+
};
|
|
10063
|
+
if (includeBadges) out.badges = badges;
|
|
10064
|
+
if (includeContributions) {
|
|
10065
|
+
out.contributionAggregates = contributionAggregates;
|
|
10066
|
+
out.lastContributionAt = lastContributionAt;
|
|
10067
|
+
}
|
|
10068
|
+
return out;
|
|
10069
|
+
}
|
|
10070
|
+
function normalizeGateInt(value, name) {
|
|
10071
|
+
if (value === void 0 || value === null) return null;
|
|
10072
|
+
const n = Number(value);
|
|
10073
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
10074
|
+
throw new Error(`invalid gate: ${name}`);
|
|
10075
|
+
}
|
|
10076
|
+
return Math.trunc(n);
|
|
10077
|
+
}
|
|
10078
|
+
function normalizeRequiredBadgeIds(value) {
|
|
10079
|
+
const list = Array.isArray(value) ? value : value != null ? [value] : [];
|
|
10080
|
+
const out = [];
|
|
10081
|
+
for (const item of list) {
|
|
10082
|
+
const id2 = toBigIntString(item, "");
|
|
10083
|
+
if (id2) out.push(id2);
|
|
10084
|
+
}
|
|
10085
|
+
return out;
|
|
10086
|
+
}
|
|
10087
|
+
function evaluate(result, gates = {}) {
|
|
10088
|
+
const reasons = [];
|
|
10089
|
+
const minBadges = normalizeGateInt(gates.minBadges, "minBadges");
|
|
10090
|
+
const minContributions = normalizeGateInt(gates.minContributions, "minContributions");
|
|
10091
|
+
const requiredBadges = normalizeRequiredBadgeIds(gates.requireBadges ?? gates.requireBadge);
|
|
10092
|
+
const badgeCount = Number(result?.signals?.badgeCount ?? (result?.badges?.length ?? 0));
|
|
10093
|
+
const totalContributions = Number(result?.signals?.totalContributions ?? (result?.contributionAggregates?.totalContributions ?? 0));
|
|
10094
|
+
if (minBadges != null) {
|
|
10095
|
+
if (!Number.isFinite(badgeCount)) {
|
|
10096
|
+
reasons.push("badges_unavailable");
|
|
10097
|
+
} else if (badgeCount < minBadges) {
|
|
10098
|
+
reasons.push(`minBadges:${badgeCount}<${minBadges}`);
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
if (minContributions != null) {
|
|
10102
|
+
if (!Number.isFinite(totalContributions)) {
|
|
10103
|
+
reasons.push("contributions_unavailable");
|
|
10104
|
+
} else if (totalContributions < minContributions) {
|
|
10105
|
+
reasons.push(`minContributions:${totalContributions}<${minContributions}`);
|
|
10106
|
+
}
|
|
10107
|
+
}
|
|
10108
|
+
if (requiredBadges.length) {
|
|
10109
|
+
const have = new Set((result?.badges || []).map((b) => toBigIntString(b?.badgeId, "")).filter(Boolean));
|
|
10110
|
+
for (const req2 of requiredBadges) {
|
|
10111
|
+
if (!have.has(req2)) reasons.push(`missingBadge:${req2}`);
|
|
10112
|
+
}
|
|
10113
|
+
}
|
|
10114
|
+
return { ok: reasons.length === 0, reasons };
|
|
10115
|
+
}
|
|
10116
|
+
module2.exports = {
|
|
10117
|
+
getByAddress,
|
|
10118
|
+
evaluate,
|
|
10119
|
+
// Expose lower-level functions for advanced usage
|
|
10120
|
+
getBadgesByRecipient,
|
|
10121
|
+
listContributions,
|
|
10122
|
+
computeAggregates
|
|
10123
|
+
};
|
|
10124
|
+
}
|
|
10125
|
+
});
|
|
10126
|
+
|
|
8658
10127
|
// src/votingMultiplier/index.js
|
|
8659
10128
|
var require_votingMultiplier = __commonJS({
|
|
8660
10129
|
"src/votingMultiplier/index.js"(exports2, module2) {
|
|
@@ -12900,6 +14369,7 @@ var require_src = __commonJS({
|
|
|
12900
14369
|
var ipns = require_ipns();
|
|
12901
14370
|
var token = require_token();
|
|
12902
14371
|
var personal = require_personal();
|
|
14372
|
+
var premium = require_premium();
|
|
12903
14373
|
var subgraph = require_subgraph();
|
|
12904
14374
|
var privateTx = require_privateTx();
|
|
12905
14375
|
var safe = require_safe();
|
|
@@ -12908,6 +14378,7 @@ var require_src = __commonJS({
|
|
|
12908
14378
|
var boost = require_boost();
|
|
12909
14379
|
var bounty = require_bounty();
|
|
12910
14380
|
var contributions = require_contributions();
|
|
14381
|
+
var reputation = require_reputation();
|
|
12911
14382
|
var votingMultiplier = require_votingMultiplier();
|
|
12912
14383
|
var auction = require_auction();
|
|
12913
14384
|
var wallet = require_wallet();
|
|
@@ -12953,6 +14424,7 @@ var require_src = __commonJS({
|
|
|
12953
14424
|
SageEchoExecutor,
|
|
12954
14425
|
token,
|
|
12955
14426
|
personal,
|
|
14427
|
+
premium,
|
|
12956
14428
|
treasury,
|
|
12957
14429
|
boost,
|
|
12958
14430
|
// bond module removed; bonds deprecated in CLI/SDK
|
|
@@ -12960,6 +14432,7 @@ var require_src = __commonJS({
|
|
|
12960
14432
|
utils: { ...utils, privateTx, safe, time },
|
|
12961
14433
|
bounty,
|
|
12962
14434
|
contributions,
|
|
14435
|
+
reputation,
|
|
12963
14436
|
votingMultiplier,
|
|
12964
14437
|
auction,
|
|
12965
14438
|
wallet: Object.assign(wallet, {
|