@zentity/fhevm-contracts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +160 -0
  2. package/abi/ComplianceRules.json +352 -0
  3. package/abi/CompliantERC20.json +493 -0
  4. package/abi/IdentityRegistry.json +712 -0
  5. package/abi/index.d.ts +3 -0
  6. package/abi/index.js +9 -0
  7. package/contracts/ARCHITECTURE.md +66 -0
  8. package/contracts/ARCHITECTURE_EXPLAINER.md +77 -0
  9. package/contracts/compliance/ComplianceRules.sol +255 -0
  10. package/contracts/core/IdentityRegistry.sol +352 -0
  11. package/contracts/interfaces/IIdentityRegistry.sol +226 -0
  12. package/contracts/mocks/.gitkeep +0 -0
  13. package/contracts/tokens/CompliantERC20.sol +379 -0
  14. package/deployments/hardhat/addresses.json +20 -0
  15. package/deployments/localhost/.chainId +1 -0
  16. package/deployments/localhost/ComplianceRules.json +662 -0
  17. package/deployments/localhost/CompliantERC20.json +888 -0
  18. package/deployments/localhost/IdentityRegistry.json +1093 -0
  19. package/deployments/localhost/solcInputs/e36969353329df673b4fae03d39e01c4.json +60 -0
  20. package/deployments/sepolia/.chainId +1 -0
  21. package/deployments/sepolia/.gitkeep +0 -0
  22. package/deployments/sepolia/ComplianceRules.json +662 -0
  23. package/deployments/sepolia/CompliantERC20.json +888 -0
  24. package/deployments/sepolia/IdentityRegistry.json +1093 -0
  25. package/deployments/sepolia/solcInputs/93d280ff0d4e798a18947a9ed6015031.json +60 -0
  26. package/dist/index.d.ts +459 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +135 -0
  29. package/package.json +110 -0
  30. package/typechain-types/@fhevm/index.ts +5 -0
  31. package/typechain-types/@fhevm/solidity/config/ZamaConfig.sol/ZamaConfig.ts +69 -0
  32. package/typechain-types/@fhevm/solidity/config/ZamaConfig.sol/ZamaEthereumConfig.ts +90 -0
  33. package/typechain-types/@fhevm/solidity/config/ZamaConfig.sol/index.ts +5 -0
  34. package/typechain-types/@fhevm/solidity/config/index.ts +5 -0
  35. package/typechain-types/@fhevm/solidity/index.ts +7 -0
  36. package/typechain-types/@fhevm/solidity/lib/FHE.sol/FHE.ts +112 -0
  37. package/typechain-types/@fhevm/solidity/lib/FHE.sol/IKMSVerifier.ts +108 -0
  38. package/typechain-types/@fhevm/solidity/lib/FHE.sol/index.ts +5 -0
  39. package/typechain-types/@fhevm/solidity/lib/Impl.sol/IACL.ts +190 -0
  40. package/typechain-types/@fhevm/solidity/lib/Impl.sol/IFHEVMExecutor.ts +623 -0
  41. package/typechain-types/@fhevm/solidity/lib/Impl.sol/IInputVerifier.ts +90 -0
  42. package/typechain-types/@fhevm/solidity/lib/Impl.sol/index.ts +6 -0
  43. package/typechain-types/@fhevm/solidity/lib/index.ts +7 -0
  44. package/typechain-types/common.ts +131 -0
  45. package/typechain-types/contracts/compliance/ComplianceRules.ts +479 -0
  46. package/typechain-types/contracts/compliance/index.ts +4 -0
  47. package/typechain-types/contracts/core/IdentityRegistry.ts +874 -0
  48. package/typechain-types/contracts/core/index.ts +4 -0
  49. package/typechain-types/contracts/index.ts +11 -0
  50. package/typechain-types/contracts/interfaces/IIdentityRegistry.ts +798 -0
  51. package/typechain-types/contracts/interfaces/index.ts +4 -0
  52. package/typechain-types/contracts/tokens/CompliantERC20.sol/CompliantERC20.ts +572 -0
  53. package/typechain-types/contracts/tokens/CompliantERC20.sol/IComplianceChecker.ts +95 -0
  54. package/typechain-types/contracts/tokens/CompliantERC20.sol/index.ts +5 -0
  55. package/typechain-types/contracts/tokens/index.ts +5 -0
  56. package/typechain-types/factories/@fhevm/index.ts +4 -0
  57. package/typechain-types/factories/@fhevm/solidity/config/ZamaConfig.sol/ZamaConfig__factory.ts +69 -0
  58. package/typechain-types/factories/@fhevm/solidity/config/ZamaConfig.sol/ZamaEthereumConfig__factory.ts +43 -0
  59. package/typechain-types/factories/@fhevm/solidity/config/ZamaConfig.sol/index.ts +5 -0
  60. package/typechain-types/factories/@fhevm/solidity/config/index.ts +4 -0
  61. package/typechain-types/factories/@fhevm/solidity/index.ts +5 -0
  62. package/typechain-types/factories/@fhevm/solidity/lib/FHE.sol/FHE__factory.ts +88 -0
  63. package/typechain-types/factories/@fhevm/solidity/lib/FHE.sol/IKMSVerifier__factory.ts +54 -0
  64. package/typechain-types/factories/@fhevm/solidity/lib/FHE.sol/index.ts +5 -0
  65. package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/IACL__factory.ts +121 -0
  66. package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/IFHEVMExecutor__factory.ts +810 -0
  67. package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/IInputVerifier__factory.ts +32 -0
  68. package/typechain-types/factories/@fhevm/solidity/lib/Impl.sol/index.ts +6 -0
  69. package/typechain-types/factories/@fhevm/solidity/lib/index.ts +5 -0
  70. package/typechain-types/factories/contracts/compliance/ComplianceRules__factory.ts +437 -0
  71. package/typechain-types/factories/contracts/compliance/index.ts +4 -0
  72. package/typechain-types/factories/contracts/core/IdentityRegistry__factory.ts +777 -0
  73. package/typechain-types/factories/contracts/core/index.ts +4 -0
  74. package/typechain-types/factories/contracts/index.ts +7 -0
  75. package/typechain-types/factories/contracts/interfaces/IIdentityRegistry__factory.ts +640 -0
  76. package/typechain-types/factories/contracts/interfaces/index.ts +4 -0
  77. package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/CompliantERC20__factory.ts +581 -0
  78. package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/IComplianceChecker__factory.ts +44 -0
  79. package/typechain-types/factories/contracts/tokens/CompliantERC20.sol/index.ts +5 -0
  80. package/typechain-types/factories/contracts/tokens/index.ts +4 -0
  81. package/typechain-types/factories/index.ts +5 -0
  82. package/typechain-types/hardhat.d.ts +261 -0
  83. package/typechain-types/index.ts +32 -0
@@ -0,0 +1,379 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // solhint-disable func-name-mixedcase
3
+ pragma solidity ^0.8.27;
4
+
5
+ import {FHE, euint64, ebool, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
6
+ import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
7
+
8
+ /**
9
+ * @title CompliantERC20
10
+ * @author Gustavo Valverde
11
+ * @notice ERC20-like token with encrypted balances and compliance checks
12
+ * @dev Part of zentity-fhevm-contracts - Builder Track
13
+ *
14
+ * @custom:category token
15
+ * @custom:concept FHE.select() for branch-free compliant transfers
16
+ * @custom:difficulty advanced
17
+ *
18
+ * This contract implements a compliant token with encrypted balances.
19
+ * Transfers only succeed if both parties pass compliance checks, but
20
+ * failures are handled silently (transfer of 0) to prevent information leakage.
21
+ *
22
+ * Key patterns demonstrated:
23
+ * 1. FHE.select() for branch-free conditional logic
24
+ * 2. Combining multiple encrypted conditions with FHE.and()
25
+ * 3. Encrypted balance management
26
+ * 4. No-revert compliance (privacy-preserving failure handling)
27
+ * 5. Integration with external compliance checker
28
+ */
29
+ contract CompliantERC20 is ZamaEthereumConfig {
30
+ // ============ Token Metadata ============
31
+
32
+ /// @notice Token name
33
+ string public name;
34
+
35
+ /// @notice Token symbol
36
+ string public symbol;
37
+
38
+ /// @notice Token decimals
39
+ uint8 public constant DECIMALS = 18;
40
+
41
+ /// @notice Total supply (public for transparency)
42
+ uint256 public totalSupply;
43
+
44
+ // ============ Token State ============
45
+
46
+ /// @notice Encrypted balances
47
+ mapping(address account => euint64 balance) private balances;
48
+
49
+ /// @notice Encrypted allowances
50
+ mapping(address owner => mapping(address spender => euint64 allowance)) private allowances;
51
+
52
+ // ============ Compliance State ============
53
+
54
+ /// @notice Compliance checker interface (can be ComplianceRules or custom)
55
+ IComplianceChecker public complianceChecker;
56
+
57
+ /// @notice Owner/admin
58
+ address public owner;
59
+ /// @notice Pending owner for two-step ownership transfer
60
+ address public pendingOwner;
61
+
62
+ // ============ Events ============
63
+
64
+ /// @notice Emitted on token transfers (indexed for efficient filtering)
65
+ /// @param from Address tokens are transferred from
66
+ /// @param to Address tokens are transferred to
67
+ event Transfer(address indexed from, address indexed to);
68
+
69
+ /// @notice Emitted when spending allowance is set
70
+ /// @param owner Address of the token owner
71
+ /// @param spender Address authorized to spend
72
+ event Approval(address indexed owner, address indexed spender);
73
+
74
+ /// @notice Emitted when new tokens are minted
75
+ /// @param to Address receiving the minted tokens
76
+ /// @param amount Number of tokens minted
77
+ event Mint(address indexed to, uint256 indexed amount);
78
+
79
+ /// @notice Emitted when the compliance checker contract is updated
80
+ /// @param newChecker Address of the new compliance checker
81
+ event ComplianceCheckerUpdated(address indexed newChecker);
82
+
83
+ /// @notice Emitted when ownership transfer is initiated
84
+ /// @param currentOwner Current owner address
85
+ /// @param pendingOwner Address that can accept ownership
86
+ event OwnershipTransferStarted(address indexed currentOwner, address indexed pendingOwner);
87
+
88
+ /// @notice Emitted when ownership transfer is completed
89
+ /// @param previousOwner Previous owner address
90
+ /// @param newOwner New owner address
91
+ event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
92
+
93
+ // ============ Errors ============
94
+
95
+ /// @notice Thrown when caller is not the contract owner
96
+ error OnlyOwner();
97
+ /// @notice Thrown when caller is not the pending owner
98
+ error OnlyPendingOwner();
99
+ /// @notice Thrown when new owner is the zero address
100
+ error InvalidOwner();
101
+
102
+ /// @notice Thrown when compliance checker is required but not set
103
+ error ComplianceCheckerNotSet();
104
+ /// @notice Thrown when caller supplies an unauthorized ciphertext handle
105
+ error UnauthorizedCiphertext();
106
+ /// @notice Thrown when mint amount would exceed uint64 accounting bounds
107
+ error TotalSupplyOverflow();
108
+
109
+ // ============ Constructor ============
110
+
111
+ /**
112
+ * @notice Initialize the token
113
+ * @param tokenName Token name
114
+ * @param tokenSymbol Token symbol
115
+ * @param checker Address of the compliance checker contract
116
+ */
117
+ constructor(string memory tokenName, string memory tokenSymbol, address checker) {
118
+ name = tokenName;
119
+ symbol = tokenSymbol;
120
+ owner = msg.sender;
121
+ if (checker != address(0)) {
122
+ complianceChecker = IComplianceChecker(checker);
123
+ }
124
+ }
125
+
126
+ // ============ Admin Functions ============
127
+
128
+ /**
129
+ * @notice Set the compliance checker contract
130
+ * @param checker Address of the compliance checker
131
+ */
132
+ function setComplianceChecker(address checker) external {
133
+ if (msg.sender != owner) revert OnlyOwner();
134
+ complianceChecker = IComplianceChecker(checker);
135
+ emit ComplianceCheckerUpdated(checker);
136
+ }
137
+
138
+ /**
139
+ * @notice Initiate transfer of contract ownership
140
+ * @param newOwner Address that can accept ownership
141
+ */
142
+ function transferOwnership(address newOwner) external {
143
+ if (msg.sender != owner) revert OnlyOwner();
144
+ if (newOwner == address(0)) revert InvalidOwner();
145
+ pendingOwner = newOwner;
146
+ emit OwnershipTransferStarted(owner, newOwner);
147
+ }
148
+
149
+ /**
150
+ * @notice Accept ownership transfer
151
+ */
152
+ function acceptOwnership() external {
153
+ if (msg.sender != pendingOwner) revert OnlyPendingOwner();
154
+ address previousOwner = owner;
155
+ owner = pendingOwner;
156
+ pendingOwner = address(0);
157
+ emit OwnershipTransferred(previousOwner, owner);
158
+ }
159
+
160
+ /**
161
+ * @notice Mint tokens to an address
162
+ * @dev Only owner can mint. Compliance is NOT checked on mint.
163
+ * @param to Recipient address
164
+ * @param amount Amount to mint (plaintext)
165
+ */
166
+ function mint(address to, uint256 amount) external {
167
+ if (msg.sender != owner) revert OnlyOwner();
168
+ if (amount > type(uint64).max) revert TotalSupplyOverflow();
169
+ if (totalSupply + amount > type(uint64).max) revert TotalSupplyOverflow();
170
+
171
+ euint64 mintAmount = FHE.asEuint64(uint64(amount));
172
+ balances[to] = FHE.add(balances[to], mintAmount);
173
+ FHE.allowThis(balances[to]);
174
+ FHE.allow(balances[to], to);
175
+
176
+ totalSupply += amount;
177
+
178
+ emit Mint(to, amount);
179
+ }
180
+
181
+ // ============ Token Functions ============
182
+
183
+ /**
184
+ * @notice Transfer tokens with encrypted amount
185
+ * @dev Branch-free transfer with compliance checks
186
+ * @param to Recipient address
187
+ * @param encryptedAmount Encrypted amount to transfer
188
+ * @param inputProof Proof for encrypted input
189
+ * @return success Always returns true (actual transfer amount may be 0)
190
+ *
191
+ * Key insight: We never revert on failed compliance. Instead:
192
+ * - If compliant: transfer the requested amount
193
+ * - If not compliant: transfer 0 (no state change, no info leak)
194
+ */
195
+ function transfer(
196
+ address to,
197
+ externalEuint64 encryptedAmount,
198
+ bytes calldata inputProof
199
+ ) external returns (bool success) {
200
+ euint64 amount = FHE.fromExternal(encryptedAmount, inputProof);
201
+ return _transfer(msg.sender, to, amount);
202
+ }
203
+
204
+ /**
205
+ * @notice Transfer with euint64 amount (for approved callers)
206
+ * @param to Recipient
207
+ * @param amount Encrypted amount
208
+ * @return success Always true
209
+ */
210
+ function transfer(address to, euint64 amount) external returns (bool success) {
211
+ if (!FHE.isSenderAllowed(amount)) revert UnauthorizedCiphertext();
212
+ return _transfer(msg.sender, to, amount);
213
+ }
214
+
215
+ /**
216
+ * @notice Approve spender to transfer tokens
217
+ * @param spender Address to approve
218
+ * @param encryptedAmount Encrypted allowance amount
219
+ * @param inputProof Proof for encrypted input
220
+ * @return success Always true
221
+ */
222
+ function approve(
223
+ address spender,
224
+ externalEuint64 encryptedAmount,
225
+ bytes calldata inputProof
226
+ ) external returns (bool success) {
227
+ euint64 amount = FHE.fromExternal(encryptedAmount, inputProof);
228
+ allowances[msg.sender][spender] = amount;
229
+ FHE.allowThis(amount);
230
+ FHE.allow(amount, msg.sender);
231
+ FHE.allow(amount, spender);
232
+
233
+ emit Approval(msg.sender, spender);
234
+ return true;
235
+ }
236
+
237
+ /**
238
+ * @notice Transfer from another account (requires approval)
239
+ * @param from Source address
240
+ * @param to Destination address
241
+ * @param encryptedAmount Encrypted amount
242
+ * @param inputProof Proof for encrypted input
243
+ * @return success Always true
244
+ */
245
+ function transferFrom(
246
+ address from,
247
+ address to,
248
+ externalEuint64 encryptedAmount,
249
+ bytes calldata inputProof
250
+ ) external returns (bool success) {
251
+ euint64 amount = FHE.fromExternal(encryptedAmount, inputProof);
252
+
253
+ // Check allowance
254
+ ebool hasAllowance = FHE.le(amount, allowances[from][msg.sender]);
255
+
256
+ // Reduce allowance (branch-free)
257
+ euint64 newAllowance = FHE.select(
258
+ hasAllowance,
259
+ FHE.sub(allowances[from][msg.sender], amount),
260
+ allowances[from][msg.sender]
261
+ );
262
+ allowances[from][msg.sender] = newAllowance;
263
+ FHE.allowThis(newAllowance);
264
+ FHE.allow(newAllowance, from);
265
+ FHE.allow(newAllowance, msg.sender);
266
+
267
+ // Only transfer if allowance was sufficient
268
+ euint64 actualAmount = FHE.select(hasAllowance, amount, FHE.asEuint64(0));
269
+
270
+ return _transfer(from, to, actualAmount);
271
+ }
272
+
273
+ // ============ View Functions ============
274
+
275
+ /**
276
+ * @notice Get encrypted balance
277
+ * @param account Address to query
278
+ * @return Encrypted balance
279
+ */
280
+ function balanceOf(address account) external view returns (euint64) {
281
+ return balances[account];
282
+ }
283
+
284
+ /**
285
+ * @notice Get encrypted allowance
286
+ * @param account Owner address
287
+ * @param spender Spender address
288
+ * @return Encrypted allowance
289
+ */
290
+ function allowance(address account, address spender) external view returns (euint64) {
291
+ return allowances[account][spender];
292
+ }
293
+
294
+ /**
295
+ * @notice Get decimals
296
+ * @return Token decimals
297
+ */
298
+ function decimals() external pure returns (uint8) {
299
+ return DECIMALS;
300
+ }
301
+
302
+ // ============ Internal Functions ============
303
+
304
+ /**
305
+ * @notice Internal transfer implementation
306
+ * @dev The heart of branch-free compliance
307
+ *
308
+ * Logic flow:
309
+ * 1. Check sender compliance (if checker is set)
310
+ * 2. Check recipient compliance (if checker is set)
311
+ * 3. Check sender has sufficient balance
312
+ * 4. Combine all checks with FHE.and()
313
+ * 5. Use FHE.select() to set transfer amount:
314
+ * - If all checks pass: transfer requested amount
315
+ * - If any check fails: transfer 0
316
+ * 6. Update balances (even if amount is 0)
317
+ *
318
+ * @param from Source address
319
+ * @param to Destination address
320
+ * @param amount Encrypted amount to transfer
321
+ * @return success Always returns true (actual transfer may be 0)
322
+ */
323
+ function _transfer(
324
+ address from,
325
+ address to,
326
+ euint64 amount
327
+ ) internal returns (bool success) {
328
+ ebool canTransfer;
329
+
330
+ // Check compliance if checker is set
331
+ if (address(complianceChecker) != address(0)) {
332
+ ebool senderCompliant = complianceChecker.checkCompliance(from);
333
+ ebool recipientCompliant = complianceChecker.checkCompliance(to);
334
+ ebool bothCompliant = FHE.and(senderCompliant, recipientCompliant);
335
+
336
+ // Check sufficient balance
337
+ ebool hasSufficientBalance = FHE.le(amount, balances[from]);
338
+
339
+ // Combine all conditions
340
+ canTransfer = FHE.and(bothCompliant, hasSufficientBalance);
341
+ } else {
342
+ // No compliance checker, only check balance
343
+ canTransfer = FHE.le(amount, balances[from]);
344
+ }
345
+
346
+ // Branch-free: select actual amount or 0
347
+ euint64 actualAmount = FHE.select(canTransfer, amount, FHE.asEuint64(0));
348
+
349
+ // Update balances
350
+ euint64 newFromBalance = FHE.sub(balances[from], actualAmount);
351
+ euint64 newToBalance = FHE.add(balances[to], actualAmount);
352
+
353
+ balances[from] = newFromBalance;
354
+ balances[to] = newToBalance;
355
+
356
+ // Set permissions
357
+ FHE.allowThis(newFromBalance);
358
+ FHE.allowThis(newToBalance);
359
+ FHE.allow(newFromBalance, from);
360
+ FHE.allow(newToBalance, to);
361
+
362
+ // Always emit (hides success/failure)
363
+ emit Transfer(from, to);
364
+
365
+ return true;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * @title IComplianceChecker
371
+ * @author Gustavo Valverde
372
+ * @notice Interface for compliance checking contracts
373
+ */
374
+ interface IComplianceChecker {
375
+ /// @notice Check if a user passes compliance requirements
376
+ /// @param user Address to check compliance for
377
+ /// @return Encrypted boolean indicating compliance status
378
+ function checkCompliance(address user) external returns (ebool);
379
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "network": "hardhat",
3
+ "chainId": 31337,
4
+ "deployedAt": "2025-12-18T22:52:19.870Z",
5
+ "deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
6
+ "contracts": {
7
+ "IdentityRegistry": {
8
+ "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
9
+ "txHash": "0x4c6d0f381e453643c8f6c7c25d9df71d7d348b3cd6b84d7267c2d5bc03833d77"
10
+ },
11
+ "ComplianceRules": {
12
+ "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
13
+ "txHash": "0x6250c7b2f2cd290b65c44bb47df75865a1d35ac2feead60d8725e1c31be2965f"
14
+ },
15
+ "CompliantERC20": {
16
+ "address": "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
17
+ "txHash": "0xb4891d2c03a25f4cb8b9f7e8bf806d4e80983a91bdda206c87001d300ee720a5"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1 @@
1
+ 31337