📄 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