📄 ProjectManager.sol
D-OPEN SOVEREIGN
Documentation & Insights
This is the friendly wrapper API that is public inside the generated ABI.
addressDonationsPerProject(address addr) public view returns (uint256[] memory)
setProjectStorage(address storageAddr)
getProjectAllocatedBudget(uint16 projectId) public view returns (uint256[] memory) {
getProject(uint16 projectId) public view returns
nbProjects() public view returns (uint16)
getProjectVersion(uint16 projectId, bytes4 lang, uint16 versionNumber) public view returns (IProjectManagerStorage.Version memory) {
addProjectVersion() public onlyOwner returns (uint16)
fundAddress(address addr, uint256 amount) internal {
balance() public view returns (uint256, uint256, uint256) {
getBalanceOfAddress(address addr) public view returns (uint256 donated, uint256 allocated, uint256 remaining) {
getProjectSupportedLanguages(uint16 projectId) public view returns (bytes4[] memory) {
setProjectManagerStorage(address storageAddr) external onlyOwner
createProject() public onlyOwner returns (uint16)1/*
2 * Copyright (c) 2026 DataPond D-Library Pty Ltd. ("The Sanctuary")
3 * Non-Profit Public Library — The World Library
4 *
5 * D-SAFE Certification issued by POND Enterprise.
6 * Published under the D-Open Code Sovereign Licence v1.0 framework
7 * DataPond D-Library All exclusive Right reserved on modifiying the code - CC Attribution to data pond, Non modifiable, Non Commercial.
8 * https://registry.world.bibliotech.com/licence
9 * Source code donated to DataPond D-Library by it's author: data pond.
10 *
11 * Technical Guardian ("The Shield"): Pond Enterprise Pty Ltd. (ACN 694 747 987)
12 * All liability claims about D-SAFE certification issues coming from the published D-Safe direction must be addressed with Pond Enterprise Pty Ltd.
13 * Code Modification automatically voids the certified liability protection provided by Pond Enterprise.
14 */
15
16// SPDX-License-Identifier: Apache-2.0
17pragma solidity ^0.8.24;
18
19import "./IProjectManagerStorage.sol";
20import {Ownable} from "./Ownable.sol";
21import {ErrorLibrary} from "./ErrorLibrary.sol";
22
23/**
24 * This is the friendly wrapper API that is public inside the generated ABI.
25 *
26 * addressDonationsPerProject(address addr) public view returns (uint256[] memory)
27 * setProjectStorage(address storageAddr)
28 * getProjectAllocatedBudget(uint16 projectId) public view returns (uint256[] memory) {
29 * getProject(uint16 projectId) public view returns
30 * nbProjects() public view returns (uint16)
31 * getProjectVersion(uint16 projectId, bytes4 lang, uint16 versionNumber) public view returns (IProjectManagerStorage.Version memory) {
32 * addProjectVersion() public onlyOwner returns (uint16)
33 * fundAddress(address addr, uint256 amount) internal {
34 * balance() public view returns (uint256, uint256, uint256) {
35 * getBalanceOfAddress(address addr) public view returns (uint256 donated, uint256 allocated, uint256 remaining) {
36 * getProjectSupportedLanguages(uint16 projectId) public view returns (bytes4[] memory) {
37 * setProjectManagerStorage(address storageAddr) external onlyOwner
38 * createProject() public onlyOwner returns (uint16)
39 */
40abstract contract ProjectManager is Ownable {
41 IProjectManagerStorage public projectStorage;
42 string public constant DOMAIN_AUTHORITY = "https://datapond.earth";
43
44 constructor() {}
45
46 function setProjectStorage(address storageAddr) external onlyOwner {
47 if (storageAddr == address(0)) revert ErrorLibrary.EmptyAddress();
48 projectStorage = IProjectManagerStorage(storageAddr);
49 }
50
51 function nbProjects() public view returns (uint16) {
52 return projectStorage.nbProjects();
53 }
54
55 function voteForProject(
56 uint16 projectId,
57 bytes4 lang,
58 uint256 amount
59 ) public {
60 uint16 totalProjects = projectStorage.nbProjects();
61 if (projectId == 0 || projectId > totalProjects)
62 revert ErrorLibrary.InvalidProjectId();
63 if (amount == 0) revert ErrorLibrary.NotEnoughOnBalance();
64
65 uint256[3] memory balances = projectStorage.getUserBalances(msg.sender);
66 if (amount > balances[2]) revert ErrorLibrary.NotEnoughOnBalance();
67 if (projectStorage.getLanguageIndex(projectId, lang) == 0)
68 revert ErrorLibrary.InvalidLangVote();
69
70 balances[2] -= amount; // balance
71 balances[1] += amount; // debit total
72 projectStorage.updateUserBalances(msg.sender, balances);
73
74 projectStorage.updateUserProjectDonation(
75 projectId,
76 msg.sender,
77 amount,
78 true
79 );
80 projectStorage.updateUserTotalDonationForProject(
81 msg.sender,
82 projectId,
83 amount,
84 true
85 );
86 projectStorage.updateVotingBalance(projectId, lang, amount, true);
87 }
88
89 function addressDonationsPerProject(
90 address addr
91 ) public view returns (uint256[] memory) {
92 uint16 totalProjects = projectStorage.nbProjects();
93 if (totalProjects == 0) return new uint256[](0);
94 uint256[] memory donationsPerProject = new uint256[](totalProjects);
95 for (uint16 i = 1; i <= totalProjects; i++) {
96 donationsPerProject[i - 1] = projectStorage
97 .getUserTotalDonationForProject(addr, i);
98 }
99 return donationsPerProject;
100 }
101
102 function createProject(
103 bytes4 lang,
104 string memory uri,
105 uint32 publicationDate,
106 string memory name,
107 string memory headline,
108 string memory arweaveAddressLogo,
109 string memory arweaveAddressBanner,
110 string memory descriptionMarkdown,
111 uint256 minimumFundingBeforeStartWork,
112 uint256 maximumFunding,
113 uint8[] memory tags,
114 uint8 complexity
115 ) public onlyOwner returns (uint16) {
116 uint16 projectId = projectStorage.incrementNbProjects();
117 projectStorage.setProject(projectId);
118 projectStorage.setNbLanguages(projectId, 1);
119 projectStorage.setLanguageMapping(projectId, lang, 1);
120 projectStorage.setNbVersions(projectId, lang, 1);
121
122 projectStorage.setVersion(
123 projectId,
124 lang,
125 1,
126 IProjectManagerStorage.Version({
127 publicationDate: publicationDate,
128 uri: uri,
129 name: name,
130 headline: headline,
131 arweaveAddressLogo: arweaveAddressLogo,
132 arweaveAddressBanner: arweaveAddressBanner,
133 descriptionMarkdown: descriptionMarkdown,
134 minimumFundingBeforeStartWork: minimumFundingBeforeStartWork,
135 maximumFunding: maximumFunding,
136 tags: tags,
137 status: 9,
138 complexity: complexity
139 })
140 );
141
142 return projectId;
143 }
144
145 function getProjectSupportedLanguages(
146 uint16 projectId
147 ) public view returns (bytes4[] memory) {
148 uint16 count = projectStorage.getNbLanguages(projectId);
149 bytes4[] memory langs = new bytes4[](count);
150 for (uint16 i = 0; i < count; i++) {
151 langs[i] = projectStorage.getLanguageAtIndex(projectId, i + 1);
152 }
153 return langs;
154 }
155
156 function getProjectAllocatedBudget(
157 uint16 projectId
158 ) public view returns (uint256[] memory) {
159 uint16 count = projectStorage.getNbLanguages(projectId);
160 uint256[] memory balances = new uint256[](count);
161 for (uint16 i = 1; i <= count; i++) {
162 bytes4 lang = projectStorage.getLanguageAtIndex(projectId, i);
163 balances[i - 1] = projectStorage.getVotingBalance(projectId, lang);
164 }
165 return balances;
166 }
167
168 function getProject(
169 uint16 projectId
170 )
171 public
172 view
173 returns (
174 uint16 id,
175 uint256[] memory projectBalance,
176 IProjectManagerStorage.Version[] memory projectVersions,
177 bytes4[] memory availableLanguages
178 )
179 {
180 uint16 totalProjects = projectStorage.nbProjects();
181 if (projectId == 0 || projectId > totalProjects)
182 revert ErrorLibrary.InvalidProjectId();
183
184 uint16 langCount = projectStorage.getNbLanguages(projectId);
185 IProjectManagerStorage.Version[]
186 memory results = new IProjectManagerStorage.Version[](langCount);
187 uint256[] memory balances = new uint256[](langCount);
188 bytes4[] memory langs = new bytes4[](langCount);
189
190 for (uint16 i = 0; i < langCount; i++) {
191 bytes4 lang = projectStorage.getLanguageAtIndex(projectId, i + 1);
192 langs[i] = lang;
193 uint16 latestVersion = projectStorage.getNbVersions(
194 projectId,
195 lang
196 );
197 IProjectManagerStorage.Version memory v = projectStorage.getVersion(
198 projectId,
199 lang,
200 latestVersion
201 );
202 v.uri = string.concat(DOMAIN_AUTHORITY, v.uri);
203 results[i] = v;
204 balances[i] = projectStorage.getVotingBalance(projectId, lang);
205 }
206
207 return (projectId, balances, results, langs);
208 }
209
210 function addProjectVersion(
211 uint16 projectId,
212 bytes4 lang,
213 uint32 publicationDate,
214 string memory uri,
215 string memory name,
216 string memory headline,
217 string memory arweaveAddressLogo,
218 string memory arweaveAddressBanner,
219 string memory descriptionMarkdown,
220 uint256 minimumFundingBeforeStartWork,
221 uint256 maximumFunding,
222 uint8[] memory tags,
223 uint8 status,
224 uint8 complexity
225 ) public onlyOwner returns (uint16) {
226 uint16 totalProjects = projectStorage.nbProjects();
227 if (projectId == 0 || projectId > totalProjects)
228 revert ErrorLibrary.InvalidProjectId();
229
230 uint16 currentNbVersions;
231 if (projectStorage.getLanguageIndex(projectId, lang) == 0) {
232 uint16 nbLangs = projectStorage.getNbLanguages(projectId) + 1;
233 projectStorage.setNbLanguages(projectId, nbLangs);
234 projectStorage.setLanguageMapping(projectId, lang, nbLangs);
235 currentNbVersions = 1;
236 } else {
237 currentNbVersions =
238 projectStorage.getNbVersions(projectId, lang) +
239 1;
240 }
241
242 projectStorage.setNbVersions(projectId, lang, currentNbVersions);
243 projectStorage.setVersion(
244 projectId,
245 lang,
246 currentNbVersions,
247 IProjectManagerStorage.Version({
248 publicationDate: publicationDate,
249 uri: string.concat(DOMAIN_AUTHORITY, uri),
250 name: name,
251 headline: headline,
252 arweaveAddressLogo: arweaveAddressLogo,
253 arweaveAddressBanner: arweaveAddressBanner,
254 descriptionMarkdown: descriptionMarkdown,
255 minimumFundingBeforeStartWork: minimumFundingBeforeStartWork,
256 maximumFunding: maximumFunding,
257 tags: tags,
258 status: status,
259 complexity: complexity
260 })
261 );
262
263 return currentNbVersions;
264 }
265
266 function deleteProject(uint16 projectId, bytes4 lang) public onlyOwner returns (uint16) {
267 uint16 totalProjects = projectStorage.nbProjects();
268 if (projectId == 0 || projectId > totalProjects)
269 revert ErrorLibrary.InvalidProjectId();
270
271 uint256 balance = projectStorage.getVotingBalance(projectId, lang);
272 require(balance < 1000, "Voting balance must be < 1000");
273
274 uint16 currentNbVersions = projectStorage.getNbVersions(projectId, lang);
275 if (currentNbVersions == 0) revert ErrorLibrary.InvalidProjectId();
276
277 IProjectManagerStorage.Version memory v = projectStorage.getVersion(projectId, lang, currentNbVersions);
278
279 v.status = 0; // STATUS_DELETED
280
281 projectStorage.setNbVersions(projectId, lang, currentNbVersions + 1);
282 projectStorage.setVersion(projectId, lang, currentNbVersions + 1, v);
283
284 return currentNbVersions + 1;
285 }
286
287 function getProjectVersion(
288 uint16 projectId,
289 bytes4 lang,
290 uint16 versionNumber
291 ) public view returns (IProjectManagerStorage.Version memory) {
292 uint16 totalProjects = projectStorage.nbProjects();
293 if (projectId == 0 || projectId > totalProjects)
294 revert ErrorLibrary.InvalidProjectId();
295 IProjectManagerStorage.Version memory v = projectStorage.getVersion(
296 projectId,
297 lang,
298 versionNumber
299 );
300 v.uri = string.concat(DOMAIN_AUTHORITY, v.uri);
301 return v;
302 }
303
304 // Only called internally by the Accountant, because Accountant is ProjectManager()
305 function fundAddress(address addr, uint256 amount) internal {
306 uint256[3] memory b = projectStorage.getUserBalances(addr);
307 b[0] += amount; // credit
308 b[2] += amount; // balance
309 projectStorage.updateUserBalances(addr, b);
310 }
311
312 function fundAddressForTesting(
313 address addr,
314 uint256 amount
315 ) external onlyOwner {
316 require(block.chainid == 31337, "only on local testnet");
317 fundAddress(addr, amount);
318 }
319
320 function balance() public view returns (uint256, uint256, uint256) {
321 uint256[3] memory b = projectStorage.getUserBalances(msg.sender);
322 return (b[0], b[1], b[2]);
323 }
324
325 function getBalanceOfAddress(
326 address addr
327 )
328 public
329 view
330 returns (uint256 donated, uint256 allocated, uint256 remaining)
331 {
332 uint256[3] memory b = projectStorage.getUserBalances(addr);
333 return (b[0], b[1], b[2]);
334 }
335
336 function recordStripeDonation(
337 string memory stripePaymentId,
338 uint256 amountUSD,
339 string memory username,
340 address donor,
341 string memory country,
342 string memory language
343 ) internal {
344 projectStorage.recordStripeDonation(
345 block.timestamp / 604800,
346 IProjectManagerStorage.StripeDonation({
347 stripePaymentId: stripePaymentId,
348 amountUSD: amountUSD,
349 username: username,
350 donor: donor,
351 country: country,
352 language: language,
353 timestamp: block.timestamp,
354 chainId: block.chainid
355 })
356 );
357 }
358}
359