📄 Factory.sol
D-OPEN SOVEREIGN
Documentation & Insights
@dev Called by a new wallet generated off-chain. The transaction payload must be signed by the trusted AWS Lambda Oracle.
Note: The function is split into an external entry + internal impl to stay within
the 16-slot Yul stack limit while handling multiple calldata string parameters.@dev Called by an EXISTING wallet generated off-chain. The transaction payload must be signed by the trusted AWS Lambda Oracle.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 "./Bouncer.sol";
20import {UserBackup} from "./UserBackup.sol";
21import {ProjectManager} from "./ProjectManager.sol";
22import {Accountant} from "./Accountant.sol";
23import {ErrorLibrary} from "./ErrorLibrary.sol";
24import {Scientist} from "./Scientist.sol";
25import {Backup} from "./Backup.sol";
26import {Subscription} from "./Subscription.sol";
27
28contract Factory is Bouncer, Backup, Accountant, Scientist, Subscription {
29 address public oracleSigner;
30 mapping(string => bool) public usedPaymentIntents;
31
32 constructor(
33 address payable projectsAccount, //80%of donations goes into this wallet
34 address payable maintenanceAccount, //10% of donations goes into this wallet
35 address payable marketingAccount, // 10% of donations goes into this wallet
36 address _pyth,
37 bytes32 _ethUsdPriceId // Needed to calculate the minimum amount in $
38 )
39 Ownable(msg.sender)
40 Accountant(
41 projectsAccount,
42 maintenanceAccount,
43 marketingAccount,
44 _pyth,
45 _ethUsdPriceId
46 )
47 {
48 // Scientist() is called implicitly.
49 }
50
51 function setOracleSigner(address _signer) external onlyOwner {
52 oracleSigner = _signer;
53 }
54
55 // =====================
56 // Core app functions
57 // =====================
58 function save(
59 uint256[] calldata patches,
60 uint8 level,
61 uint16[] calldata textIndex,
62 string[] calldata textData
63 ) external {
64 // User doesn't have an account
65 if (!bouncerStorage.hasAccount(msg.sender)) {
66 revert ErrorLibrary.ErrNoAccount();
67 }
68 (, string memory country, string memory language, uint32 uId) = bouncerStorage.userInfos(
69 msg.sender
70 ); // get userId for backup
71 storeNewWrite(bouncerStorage.locationToId(country, language));
72 backupStorage.storeBatchActions(
73 uId,
74 patches,
75 level,
76 textIndex,
77 textData
78 );
79 // storeBatchStats(patches, textIndex, textData);
80 }
81
82 function loadUsername(
83 string calldata username
84 )
85 external
86 view
87 returns (
88 uint256[] memory compressedActions,
89 uint256[] memory indexes,
90 string[] memory stringIndexes
91 )
92 {
93 uint32 uId = bouncerStorage.usernameToUserId(username);
94 if (uId == 0) revert ErrorLibrary.UsernameNotDefined();
95 return backupStorage.loadActions(uId);
96 }
97
98 function loadUsernameSince(
99 string calldata username,
100 uint16 index
101 )
102 external
103 view
104 returns (
105 uint256[] memory compressedActions,
106 uint256[] memory indexes,
107 string[] memory stringIndexes
108 )
109 {
110 uint32 uId = bouncerStorage.usernameToUserId(username);
111 if (uId == 0) revert ErrorLibrary.UsernameNotDefined();
112 return backupStorage.loadActionsSince(uId, index);
113 }
114
115 function register(
116 string calldata country,
117 string calldata language,
118 uint32 referredBy
119 )
120 public
121 payable
122 returns (string memory username, uint32 userId, address addr)
123 {
124 // The accountant handles the transaction
125 donateFunds();
126 // The transaction is funded successfully, now generate new identity and register new account
127 (
128 string memory _username,
129 string memory _country,
130 string memory _language,
131 uint32 newUserId
132 ) = bouncerStorage.newAccount(msg.sender, country, language, referredBy);
133 // Update the global stats with the scientist
134 storeNewRegister(bouncerStorage.locationToId(_country, _language));
135 return (_username, newUserId, msg.sender);
136 }
137
138 /**
139 * @dev Called by a new wallet generated off-chain. The transaction payload must be signed by the trusted AWS Lambda Oracle.
140 * Note: The function is split into an external entry + internal impl to stay within
141 * the 16-slot Yul stack limit while handling multiple calldata string parameters.
142 */
143 function registerStripe(
144 string calldata country,
145 string calldata language,
146 string calldata paymentIntentId,
147 uint256 votes,
148 uint32 referredBy,
149 bytes calldata signature
150 ) external returns (string memory username, uint32 userId, address addr) {
151 // Verify oracle signature (uses calldata strings on the stack here, then drops them)
152 bytes32 msgHash = keccak256(abi.encodePacked(
153 block.chainid, address(this), msg.sender, country, language, paymentIntentId, votes, referredBy
154 ));
155 _verifyOracleSignature(msgHash, signature);
156
157 // Delegate to impl — only passes memory copies, freeing calldata slot pressure
158 return _registerStripeImpl(country, language, paymentIntentId, votes, referredBy);
159 }
160
161 function _registerStripeImpl(
162 string calldata country,
163 string calldata language,
164 string calldata paymentIntentId,
165 uint256 votes,
166 uint32 referredBy
167 ) internal returns (string memory, uint32, address) {
168 require(!usedPaymentIntents[paymentIntentId], "Stripe payment already claimed!");
169 usedPaymentIntents[paymentIntentId] = true;
170
171 fundAddress(msg.sender, votes);
172
173 (
174 string memory _username,
175 string memory _country,
176 string memory _language,
177 uint32 newUserId
178 ) = bouncerStorage.newAccount(msg.sender, country, language, referredBy);
179 storeNewRegister(bouncerStorage.locationToId(_country, _language));
180 recordStripeDonation(paymentIntentId, votes, _username, msg.sender, country, language);
181
182 return (_username, newUserId, msg.sender);
183 }
184
185 /// @dev Verifies an AWS Lambda oracle ECDSA signature against a pre-computed message hash.
186 function _verifyOracleSignature(bytes32 messageHash, bytes calldata signature) internal view {
187 bytes32 ethSignedMessageHash = keccak256(
188 abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
189 );
190 address recoveredSigner = _recoverSigner(ethSignedMessageHash, signature);
191 require(
192 recoveredSigner == oracleSigner && oracleSigner != address(0),
193 "Invalid signature: Not authorized by D-Library"
194 );
195 }
196
197 /**
198 * @dev Called by an EXISTING wallet generated off-chain. The transaction payload must be signed by the trusted AWS Lambda Oracle.
199 */
200 function fundStripe(
201 string calldata paymentIntentId,
202 uint256 votes,
203 bytes calldata signature
204 ) external {
205 // 1. Ensure the user exists
206 require(bouncerStorage.hasAccount(msg.sender), "Must be registered");
207
208 // 2. Verify Oracle Signature
209 bytes32 msgHash = keccak256(abi.encodePacked(block.chainid, address(this), msg.sender, paymentIntentId, votes));
210 _verifyOracleSignature(msgHash, signature);
211
212 // 3. Verify Payment Intent (Replay Protection)
213 require(!usedPaymentIntents[paymentIntentId], "Stripe payment already claimed!");
214 usedPaymentIntents[paymentIntentId] = true;
215
216 // 4. Grant Voting Power
217 fundAddress(msg.sender, votes);
218
219 // 5. Record the Donation in Weekly Storage
220 IBouncerStorage.UserAccount memory user = bouncerStorage.userData(bouncerStorage.userIdOf(msg.sender));
221 recordStripeDonation(paymentIntentId, votes, user.username, msg.sender, user.country, user.language);
222 }
223
224 // Helper function for ECDSA recovery
225 function _recoverSigner(
226 bytes32 _ethSignedMessageHash,
227 bytes memory _signature
228 ) internal pure returns (address) {
229 (bytes32 r, bytes32 s, uint8 v) = _splitSignature(_signature);
230 return ecrecover(_ethSignedMessageHash, v, r, s);
231 }
232
233 function _splitSignature(
234 bytes memory sig
235 ) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
236 require(sig.length == 65, "Invalid signature length");
237
238 assembly {
239 /*
240 First 32 bytes stores the length of the signature
241
242 add(sig, 32) = pointer of sig + 32
243 effectively, skips first 32 bytes of signature
244
245 mload(p) loads next 32 bytes starting at the memory address p into memory
246 */
247
248 // first 32 bytes, after the length prefix
249 r := mload(add(sig, 32))
250 // second 32 bytes
251 s := mload(add(sig, 64))
252 // final byte (first byte of the next 32 bytes)
253 v := byte(0, mload(add(sig, 96)))
254 }
255
256 // implicitly return (r, s, v)
257 }
258
259 function load()
260 external
261 view
262 returns (
263 uint256[] memory compressedActions,
264 uint256[] memory indexes,
265 string[] memory stringIndexes
266 )
267 {
268 // Account must exist
269 if (!bouncerStorage.hasAccount(msg.sender)) {
270 revert ErrorLibrary.ErrNoAccount();
271 }
272 (, , , uint32 uId) = bouncerStorage.userInfos(msg.sender);
273 return backupStorage.loadActions(uId);
274 }
275
276 function stats(
277 ActionType action,
278 uint8 objectType,
279 uint16 monthId
280 ) external view returns (uint64[] memory hashes, uint32[] memory scores) {
281 // Access ScientistStorage directly since getStatsRaw was removed from Scientist
282 return scientistStorage.getStatsRaw(uint8(action), objectType, monthId);
283 }
284
285 function nbWritesByUsername(
286 string calldata username
287 ) external view returns (uint256) {
288 uint32 uId = bouncerStorage.usernameToUserId(username);
289 if (uId == 0) revert ErrorLibrary.UsernameNotDefined();
290 return backupStorage.nbWrites(uId);
291 }
292
293 // =====================
294 // Subscription functions
295 // =====================
296
297 function subscribeTo(string calldata username) external {
298 if (!bouncerStorage.hasAccount(msg.sender)) revert ErrorLibrary.ErrNoAccount();
299 uint32 targetId = bouncerStorage.usernameToUserId(username);
300 if (targetId == 0) revert ErrorLibrary.UsernameNotDefined();
301 (, , , uint32 subscriberId) = bouncerStorage.userInfos(msg.sender);
302 subscriptionManager.subscribeTo(subscriberId, targetId);
303 }
304
305 function unsubscribeFrom(string calldata username) external {
306 if (!bouncerStorage.hasAccount(msg.sender)) revert ErrorLibrary.ErrNoAccount();
307 uint32 targetId = bouncerStorage.usernameToUserId(username);
308 if (targetId == 0) revert ErrorLibrary.UsernameNotDefined();
309 (, , , uint32 subscriberId) = bouncerStorage.userInfos(msg.sender);
310 subscriptionManager.unsubscribeFrom(subscriberId, targetId);
311 }
312 function getPatchesSubscriptionFrom(string calldata username) external view returns (
313 uint256[] memory mergedPatches,
314 uint256[] memory mergedStringPositions,
315 string[] memory mergedStrings
316 ) {
317 uint32 rootId = bouncerStorage.usernameToUserId(username);
318 if (rootId == 0) revert ErrorLibrary.UsernameNotDefined();
319
320 uint32[] memory discovered = _getSubscribedNetwork(rootId);
321
322 return _mergeNetworkPatches(discovered);
323 }
324
325 function _getSubscribedNetwork(uint32 rootId) internal view returns (uint32[] memory) {
326 uint32[] memory queue = new uint32[](100);
327 uint32[] memory discovered = new uint32[](100);
328 uint256 head = 0;
329 uint256 tail = 0;
330 uint256 discCount = 0;
331
332 queue[tail++] = rootId;
333 discovered[discCount++] = rootId;
334
335 while (head < tail && discCount < 100) {
336 uint32 u = queue[head++];
337 uint32[] memory children = subscriptionManager.getSubscriptions(u);
338 uint256 childLen = children.length;
339 for (uint256 i = 0; i < childLen; i++) {
340 uint32 v = children[i];
341 if (v != 0) {
342 // Check if already discovered
343 bool found = false;
344 for (uint256 j = 0; j < discCount; j++) {
345 if (discovered[j] == v) {
346 found = true;
347 break;
348 }
349 }
350 if (!found && discCount < 100) {
351 discovered[discCount++] = v;
352 queue[tail++] = v;
353 }
354 }
355 }
356 }
357
358 uint32[] memory result = new uint32[](discCount);
359 for (uint256 i = 0; i < discCount; i++) {
360 result[i] = discovered[i];
361 }
362 return result;
363 }
364
365 function _mergeNetworkPatches(uint32[] memory discovered) internal view returns (
366 uint256[] memory mergedPatches,
367 uint256[] memory mergedStringPositions,
368 string[] memory mergedStrings
369 ) {
370 uint256 totalPatches = 0;
371 uint256 totalStrings = 0;
372 uint256 discCount = discovered.length;
373
374 for (uint256 i = 0; i < discCount; i++) {
375 (uint256[] memory patches, , string[] memory strings) = backupStorage.loadActions(discovered[i]);
376 totalPatches += patches.length;
377 totalStrings += strings.length;
378 }
379
380 mergedPatches = new uint256[](totalPatches);
381 mergedStringPositions = new uint256[](totalStrings);
382 mergedStrings = new string[](totalStrings);
383
384 uint256 patchOffset = 0;
385 uint256 strOffset = 0;
386
387 for (uint256 i = 0; i < discCount; i++) {
388 (uint256[] memory patches, uint256[] memory strPos, string[] memory strs) = backupStorage.loadActions(discovered[i]);
389 uint256 n = patches.length;
390 uint256 m = strs.length;
391
392 for (uint256 k = 0; k < n; k++) {
393 mergedPatches[patchOffset + k] = patches[n - 1 - k];
394 }
395
396 for (uint256 k = 0; k < m; k++) {
397 uint256 revK = m - 1 - k;
398 uint256 originalPos = strPos[revK];
399 uint256 newPosInReversed = n - 1 - originalPos;
400
401 mergedStringPositions[strOffset + k] = patchOffset + newPosInReversed;
402 mergedStrings[strOffset + k] = strs[revK];
403 }
404
405 patchOffset += n;
406 strOffset += m;
407 }
408 }
409}
410